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,47 @@
|
|
|
1
|
+
# Asks for singular scope names, except under the `with_` prefix. A scope reads
|
|
2
|
+
# as an adjective on the relation, `Person.customer` the way `Person.active`
|
|
3
|
+
# does, while a plural name restates the collection the relation already is. A
|
|
4
|
+
# `with_` scope names what the records carry, and that is plural as often as
|
|
5
|
+
# not.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad - plural scope name
|
|
9
|
+
# scope :customers, -> { where(customer: true) }
|
|
10
|
+
# scope :admins, -> { where(role: :admin) }
|
|
11
|
+
#
|
|
12
|
+
# # good - singular scope name
|
|
13
|
+
# scope :customer, -> { where(customer: true) }
|
|
14
|
+
# scope :admin, -> { where(role: :admin) }
|
|
15
|
+
#
|
|
16
|
+
# # good - with_* prefix can be plural (for associations)
|
|
17
|
+
# scope :with_posts, -> { includes(:posts) }
|
|
18
|
+
# scope :with_comments, -> { preload(:comments) }
|
|
19
|
+
#
|
|
20
|
+
class RuboCop::Cop::Callbacksystems::SingularScopeNames < RuboCop::Cop::Callbacksystems::Base
|
|
21
|
+
MESSAGE = "Scope names should be singular. Use `%<singular>s` instead of `%<name>s`."
|
|
22
|
+
|
|
23
|
+
def on_send(node)
|
|
24
|
+
add_offense(node.first_argument, message: message_for(node.first_argument.value)) if plural_scope?(node)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
alias on_csend on_send
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
ASSOCIATION_PREFIX = "with_"
|
|
31
|
+
|
|
32
|
+
def plural_scope?(node)
|
|
33
|
+
scope_definition?(node) && plural?(node.first_argument.value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def plural?(name)
|
|
37
|
+
!name.start_with?(ASSOCIATION_PREFIX) && singular_of(name) != name.to_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def singular_of(name)
|
|
41
|
+
name.to_s.singularize
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def message_for(name)
|
|
45
|
+
format(MESSAGE, singular: singular_of(name), name:)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Requires a space inside the delimiters of a percent literal, the way an array
|
|
2
|
+
# literal carries one. `Layout/SpaceInsideArrayLiteralBrackets` asks for `[ a, b ]`
|
|
3
|
+
# and `Layout/SpaceInsideHashLiteralBraces` for `{ a: 1 }`, while the core cop for
|
|
4
|
+
# these literals can only forbid the space, so this one asks for it and that one
|
|
5
|
+
# stays off.
|
|
6
|
+
#
|
|
7
|
+
# A literal spanning several lines is left alone, since the line breaks already
|
|
8
|
+
# separate its elements, and so is an empty one, which has nothing to separate.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad
|
|
12
|
+
# %i[save update]
|
|
13
|
+
# %w[a b]
|
|
14
|
+
#
|
|
15
|
+
# # good
|
|
16
|
+
# %i[ save update ]
|
|
17
|
+
# %w[ a b ]
|
|
18
|
+
#
|
|
19
|
+
# # good - the line breaks separate these
|
|
20
|
+
# %i[
|
|
21
|
+
# save update
|
|
22
|
+
# destroy
|
|
23
|
+
# ]
|
|
24
|
+
#
|
|
25
|
+
# # good - nothing to separate
|
|
26
|
+
# %i[]
|
|
27
|
+
#
|
|
28
|
+
class RuboCop::Cop::Callbacksystems::SpaceInsidePercentLiteral < RuboCop::Cop::Callbacksystems::Base
|
|
29
|
+
extend RuboCop::Cop::AutoCorrector
|
|
30
|
+
|
|
31
|
+
def on_array(node)
|
|
32
|
+
report PercentLiteral.new(node) if node.percent_literal?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
class PercentLiteral
|
|
37
|
+
include RuboCop::Callbacksystems::Helpers
|
|
38
|
+
|
|
39
|
+
MESSAGE = "Add a space inside the delimiters, the way an array literal carries one."
|
|
40
|
+
|
|
41
|
+
def initialize(node)
|
|
42
|
+
@node = node
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def offense
|
|
46
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE) { correct(it) } if unspaced?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
attr_reader :node
|
|
51
|
+
|
|
52
|
+
def unspaced?
|
|
53
|
+
eligible? && [ opening_gap, closing_gap ].any? { it.source != " " }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def eligible?
|
|
57
|
+
node.children.any? && same_line?(node.loc.begin, node.loc.end)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def opening_gap
|
|
61
|
+
node.loc.begin.end.join(node.children.first.source_range.begin)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def closing_gap
|
|
65
|
+
node.children.last.source_range.end.join(node.loc.end.begin)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def correct(corrector)
|
|
69
|
+
corrector.replace(opening_gap, " ")
|
|
70
|
+
corrector.replace(closing_gap, " ")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Keeps system tests on a real browser driver.
|
|
2
|
+
# `rack_test` skips JavaScript entirely, so a Hotwire application under it
|
|
3
|
+
# exercises pages that never behave like production. Integration tests
|
|
4
|
+
# already cover the fast no-browser path.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - system tests without a browser
|
|
8
|
+
# class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|
9
|
+
# driven_by :rack_test
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# # good - headless browser
|
|
13
|
+
# class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|
14
|
+
# driven_by :selenium, using: :headless_chrome
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
class RuboCop::Cop::Callbacksystems::SystemTestBrowserDriver < RuboCop::Cop::Callbacksystems::Base
|
|
18
|
+
MESSAGE = "System tests must drive a real browser. Replace `:rack_test` with `:selenium, using: :headless_chrome`."
|
|
19
|
+
|
|
20
|
+
# @!method rack_test_driver?(node)
|
|
21
|
+
def_node_matcher :rack_test_driver?, <<~PATTERN
|
|
22
|
+
(call {nil? (self)} :driven_by (sym :rack_test) ...)
|
|
23
|
+
PATTERN
|
|
24
|
+
|
|
25
|
+
def on_send(node)
|
|
26
|
+
add_offense(node, message: MESSAGE) if rack_test_driver?(node)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias on_csend on_send
|
|
30
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Asks for `test "description" do` over `def test_*`, and for `setup` and
|
|
2
|
+
# `teardown` blocks over methods of those names. A description in plain words
|
|
3
|
+
# says more than an underscored method name, and a `setup` block runs beside
|
|
4
|
+
# the ones a parent class declares, where a `setup` method replaces them unless
|
|
5
|
+
# it remembers to call `super`.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad
|
|
9
|
+
# def test_something
|
|
10
|
+
# assert true
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# # good
|
|
14
|
+
# test "something" do
|
|
15
|
+
# assert true
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # bad
|
|
19
|
+
# def setup
|
|
20
|
+
# @user = users(:bruno)
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# setup do
|
|
25
|
+
# @user = users(:bruno)
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# # bad
|
|
29
|
+
# def teardown
|
|
30
|
+
# cleanup
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# # good
|
|
34
|
+
# teardown do
|
|
35
|
+
# cleanup
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
class RuboCop::Cop::Callbacksystems::TestMethodDefinition < RuboCop::Cop::Callbacksystems::Base
|
|
39
|
+
TEST_MESSAGE = "Use `test \"description\" do` instead of `def %<method>s`."
|
|
40
|
+
BLOCK_MESSAGE = "Use `%<method>s do` block instead of `def %<method>s`."
|
|
41
|
+
|
|
42
|
+
def on_def(node)
|
|
43
|
+
report MethodDefinition.new(node)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
class MethodDefinition
|
|
48
|
+
include RuboCop::Callbacksystems::Helpers
|
|
49
|
+
|
|
50
|
+
TEST_PREFIX = "test_"
|
|
51
|
+
HOOK_METHODS = %i[ setup teardown ]
|
|
52
|
+
|
|
53
|
+
def initialize(node)
|
|
54
|
+
@node = node
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def offense
|
|
58
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if inside_test_class? && message
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
attr_reader :node
|
|
63
|
+
|
|
64
|
+
def inside_test_class?
|
|
65
|
+
class_node = enclosing_class_or_module_of(node)
|
|
66
|
+
direct_method_definition?(node) && class_node.class_type? && (class_name_of(class_node).end_with?("Test") ||
|
|
67
|
+
rails_test_base_class?(class_node.parent_class))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def message
|
|
71
|
+
if test_method?
|
|
72
|
+
format(TEST_MESSAGE, method: node.method_name)
|
|
73
|
+
elsif hook_method?
|
|
74
|
+
format(BLOCK_MESSAGE, method: node.method_name)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_method?
|
|
79
|
+
node.method_name.start_with?(TEST_PREFIX)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def hook_method?
|
|
83
|
+
HOOK_METHODS.include?(node.method_name)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Ensures tests follow the order of the methods they cover in the source file.
|
|
2
|
+
# A test file read beside its source then walks the class top to bottom, and a
|
|
3
|
+
# reader looking for the tests of a method finds them where the method sits
|
|
4
|
+
# instead of scanning the whole file for its name.
|
|
5
|
+
#
|
|
6
|
+
# A test names its method with the first word of its description, so only the
|
|
7
|
+
# tests opening with a method of the source are read, and the cop runs on test
|
|
8
|
+
# files whose source file exists.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # Source file (app/models/user.rb):
|
|
12
|
+
# # def name; end
|
|
13
|
+
# # def email; end
|
|
14
|
+
# # def admin?; end
|
|
15
|
+
#
|
|
16
|
+
# # bad - tests not in method order
|
|
17
|
+
# test "email returns formatted email" do
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# test "name returns full name" do
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# # good - tests follow method order
|
|
24
|
+
# test "name returns full name" do
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# test "email returns formatted email" do
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# test "admin? returns true for admins" do
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
class RuboCop::Cop::Callbacksystems::TestMethodOrder < RuboCop::Cop::Callbacksystems::Base
|
|
34
|
+
extend RuboCop::Cop::AutoCorrector
|
|
35
|
+
include RuboCop::Callbacksystems::Testing::CopHelpers
|
|
36
|
+
|
|
37
|
+
def external_dependency_checksum
|
|
38
|
+
RuboCop::Callbacksystems::Source::FilesChecksum.for([ "app/**/*.rb", "lib/**/*.rb" ], root: project_root)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def on_new_investigation
|
|
42
|
+
@method_positions = nil
|
|
43
|
+
@path_mapping = nil
|
|
44
|
+
|
|
45
|
+
report_each TestOrder.new(processed_source, blocks: test_blocks, positions: method_positions)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def method_positions
|
|
50
|
+
@method_positions ||= checkable? ? source_methods.each_with_index.to_a.uniq(&:first).to_h : {}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def checkable?
|
|
54
|
+
test_file?(processed_source.file_path) && path_mapping.source_path && File.exist?(path_mapping.source_path)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def path_mapping
|
|
58
|
+
@path_mapping ||= RuboCop::Callbacksystems::Testing::PathMapping.new(processed_source.file_path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def source_methods
|
|
62
|
+
source_methods_for(path_mapping.source_path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def source_methods_for(source_path)
|
|
66
|
+
source = RuboCop::Callbacksystems::Source::FileAst.processed_source \
|
|
67
|
+
source_path, ruby_version: processed_source.ruby_version
|
|
68
|
+
|
|
69
|
+
source&.valid_syntax? ? RuboCop::Callbacksystems::Methods::Definitions.new(source.ast).map(&:name) : []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class TestOrder
|
|
73
|
+
Test = Data.define(:node, :name, :position)
|
|
74
|
+
|
|
75
|
+
def initialize(processed_source, blocks:, positions:)
|
|
76
|
+
@processed_source = processed_source
|
|
77
|
+
@blocks = blocks
|
|
78
|
+
@positions = positions
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Only the first offense carries the correction, since rewriting puts every test in order at once.
|
|
82
|
+
def each_offense
|
|
83
|
+
correction_available = reordering.applicable?
|
|
84
|
+
offenses.each do |offense|
|
|
85
|
+
yield correction_available ? correcting(offense) : offense
|
|
86
|
+
correction_available = false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
attr_reader :processed_source, :blocks, :positions
|
|
92
|
+
|
|
93
|
+
delegate :rewrite, to: :reordering, private: true
|
|
94
|
+
|
|
95
|
+
def reordering
|
|
96
|
+
@reordering ||= Reordering.new(processed_source, blocks:, tests:)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def tests
|
|
100
|
+
@tests ||= blocks.filter_map { test_for(it) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_for(block)
|
|
104
|
+
Description.new(block, positions).method_name&.then { Test.new(block, it, positions.fetch(it)) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def offenses
|
|
108
|
+
placements.filter_map(&:offense)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def placements
|
|
112
|
+
Placements.new(tests)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def correcting(offense)
|
|
116
|
+
RuboCop::Callbacksystems::Offense.new(offense.range, offense.message) { rewrite(it) }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Any executable statement between two tests is a hard boundary, so a correction cannot move a test across setup.
|
|
121
|
+
class Reordering
|
|
122
|
+
include RuboCop::Callbacksystems::Helpers
|
|
123
|
+
|
|
124
|
+
def initialize(processed_source, blocks:, tests:)
|
|
125
|
+
@processed_source = processed_source
|
|
126
|
+
@blocks = blocks
|
|
127
|
+
@tests = tests
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def rewrite(corrector)
|
|
131
|
+
runs.select(&:reorderable?).each { it.rewrite(corrector) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def applicable?
|
|
135
|
+
runs.any?(&:reorderable?)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
attr_reader :processed_source, :blocks, :tests
|
|
140
|
+
|
|
141
|
+
def runs
|
|
142
|
+
@runs ||= blocks.sort_by { it.source_range.begin_pos }
|
|
143
|
+
.slice_when { |left, right| interrupted_between?(left, right) }
|
|
144
|
+
.map { Run.new(it, tests, processed_source) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def interrupted_between?(left, right)
|
|
148
|
+
left_end = statement_of(left).range.end_pos
|
|
149
|
+
right_start = statement_of(right).begin_position
|
|
150
|
+
right_start < left_end || content_in?(range_between(left_end, right_start).source)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def statement_of(node)
|
|
154
|
+
RuboCop::Callbacksystems::Source::StatementWithComments.new(node, processed_source)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def content_in?(source)
|
|
158
|
+
source.lines.any? { it.strip.present? }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# One uninterrupted run of test blocks, where a test the source does not know keeps its slot.
|
|
163
|
+
class Run
|
|
164
|
+
def initialize(blocks, tests, processed_source)
|
|
165
|
+
@blocks = blocks
|
|
166
|
+
@tests = tests
|
|
167
|
+
@processed_source = processed_source
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def rewrite(corrector)
|
|
171
|
+
moved_indexes.each { corrector.replace(statement_of(recognized[it]).range, statement_of(ordered[it]).source) }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def reorderable?
|
|
175
|
+
recognized != ordered && tooling_scope_kept?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
attr_reader :blocks, :tests, :processed_source
|
|
180
|
+
|
|
181
|
+
def moved_indexes
|
|
182
|
+
recognized.each_index.reject { recognized[it].equal?(ordered[it]) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def recognized
|
|
186
|
+
@recognized ||= blocks.filter_map { tests_by_node[it] }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def tests_by_node
|
|
190
|
+
@tests_by_node ||= {}.compare_by_identity.tap do |index|
|
|
191
|
+
tests.each { index[it.node] = it }
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def ordered
|
|
196
|
+
@ordered ||= recognized.sort_by.with_index { |test, index| [ test.position, index ] }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def statement_of(test)
|
|
200
|
+
statement_for(test.node)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def statement_for(node)
|
|
204
|
+
RuboCop::Callbacksystems::Source::StatementWithComments.new(node, processed_source)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def tooling_scope_kept?
|
|
208
|
+
blocks.none? { statement_for(it).contains_tooling_comment? }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
class Description
|
|
213
|
+
include RuboCop::Callbacksystems::Helpers
|
|
214
|
+
|
|
215
|
+
def initialize(block, positions)
|
|
216
|
+
@block = block
|
|
217
|
+
@positions = positions
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def method_name
|
|
221
|
+
candidates.find { positions.key?(it) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private
|
|
225
|
+
attr_reader :block, :positions
|
|
226
|
+
|
|
227
|
+
# A scope is named bare where a method might carry a `?` or `!`, so the word after the name tells the two apart.
|
|
228
|
+
def candidates
|
|
229
|
+
if first_word.nil? then []
|
|
230
|
+
elsif scope? then [ first_word.to_sym ]
|
|
231
|
+
else [ first_word.to_sym, :"#{first_word}?", :"#{first_word}!" ]
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def first_word
|
|
236
|
+
words.first
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def words
|
|
240
|
+
@words ||= block.send_node.first_argument.value.split(/\s+/)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def scope?
|
|
244
|
+
words.second == "scope"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Placements read the prefix once, carrying exactly the facts the next test needs.
|
|
249
|
+
class Placements
|
|
250
|
+
include Enumerable
|
|
251
|
+
|
|
252
|
+
delegate :each, to: :placements
|
|
253
|
+
|
|
254
|
+
def initialize(tests)
|
|
255
|
+
@tests = tests
|
|
256
|
+
@seen_names = Set.new
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
private
|
|
260
|
+
attr_reader :tests, :seen_names, :previous, :anchor
|
|
261
|
+
|
|
262
|
+
def placements
|
|
263
|
+
@placements ||= tests.map { |test| placement_for(test).tap { remember(test) } }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def placement_for(test)
|
|
267
|
+
Placement.new(test, repeated: seen_names.include?(test.name), grouped: previous&.name == test.name, anchor:)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def remember(test)
|
|
271
|
+
seen_names << test.name
|
|
272
|
+
@previous = test
|
|
273
|
+
@anchor = test if anchor.nil? || test.position > anchor.position
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
class Placement
|
|
278
|
+
GROUPING_MESSAGE = "Tests for `%<method>s` should be grouped together."
|
|
279
|
+
ORDER_MESSAGE = "Test for `%<method>s` appears after `%<previous>s`, but `%<method>s` is defined first in source."
|
|
280
|
+
|
|
281
|
+
def initialize(test, repeated:, grouped:, anchor:)
|
|
282
|
+
@test = test
|
|
283
|
+
@repeated = repeated
|
|
284
|
+
@grouped = grouped
|
|
285
|
+
@anchor = anchor
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def offense
|
|
289
|
+
RuboCop::Callbacksystems::Offense.new(test.node, message) if separated? || out_of_order?
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
private
|
|
293
|
+
attr_reader :test, :anchor, :repeated, :grouped
|
|
294
|
+
|
|
295
|
+
def separated?
|
|
296
|
+
repeated? && !grouped?
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def repeated?
|
|
300
|
+
repeated
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def grouped?
|
|
304
|
+
grouped
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def out_of_order?
|
|
308
|
+
!repeated? && anchor && test.position < anchor.position
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def message
|
|
312
|
+
separated? ? format(GROUPING_MESSAGE, method: test.name) : order_message
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def order_message
|
|
316
|
+
format(ORDER_MESSAGE, method: test.name, previous: anchor.name)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Flags private methods with too many instance variable assignments.
|
|
2
|
+
# Too many instance variable assignments often indicates a method is doing too much.
|
|
3
|
+
# The `initialize` method and public methods are excluded.
|
|
4
|
+
# Public methods (like controller actions) often need multiple instance variables.
|
|
5
|
+
#
|
|
6
|
+
# @example MaxAssignments: 2 (default)
|
|
7
|
+
# # bad - too many instance variable assignments in private method
|
|
8
|
+
# private
|
|
9
|
+
# def setup_request
|
|
10
|
+
# @user = find_user
|
|
11
|
+
# @account = find_account
|
|
12
|
+
# @permissions = load_permissions
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# # good - single responsibility
|
|
16
|
+
# private
|
|
17
|
+
# def set_user
|
|
18
|
+
# @user = find_user
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# # good - public methods can have many (e.g., controller actions)
|
|
22
|
+
# def show
|
|
23
|
+
# @user = find_user
|
|
24
|
+
# @account = find_account
|
|
25
|
+
# @permissions = load_permissions
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# # good - initialize can have many
|
|
29
|
+
# def initialize(user, account)
|
|
30
|
+
# @user = user
|
|
31
|
+
# @account = account
|
|
32
|
+
# @created_at = Time.current
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
class RuboCop::Cop::Callbacksystems::TooManyInstanceVariables < RuboCop::Cop::Callbacksystems::Base
|
|
36
|
+
def on_def(node)
|
|
37
|
+
report InstanceVariableAssignments.new(node, max: cop_config["MaxAssignments"])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
alias on_defs on_def
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
class InstanceVariableAssignments
|
|
44
|
+
include RuboCop::Callbacksystems::Helpers
|
|
45
|
+
|
|
46
|
+
MESSAGE = "Method has %<count>d instance variable assignments (max %<max>d). " \
|
|
47
|
+
"Consider splitting into separate methods."
|
|
48
|
+
|
|
49
|
+
def initialize(node, max:)
|
|
50
|
+
@node = node
|
|
51
|
+
@max = max
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def offense
|
|
55
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if private_non_initializer? && count > max
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
attr_reader :node, :max
|
|
60
|
+
|
|
61
|
+
def private_non_initializer?
|
|
62
|
+
direct_method_definition?(node) && !node.method?(:initialize) && !public_method?(node)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def count
|
|
66
|
+
@count ||= instance_assignments.to_set(&:name).size
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def instance_assignments
|
|
70
|
+
RuboCop::Callbacksystems::Execution::Immediate.new(node.body).nodes_of_type(:ivasgn)
|
|
71
|
+
.select { self_preserved_between?(it, boundary: node) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def message
|
|
75
|
+
format(MESSAGE, count:, max:)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Flags methods with too many local variable assignments.
|
|
2
|
+
# Too many local variables often indicates imperative code that could
|
|
3
|
+
# be made more declarative by extracting methods.
|
|
4
|
+
#
|
|
5
|
+
# @example MaxAssignments: 3 (default)
|
|
6
|
+
# # bad - imperative with local variables
|
|
7
|
+
# def process
|
|
8
|
+
# user = find_user
|
|
9
|
+
# account = user.account
|
|
10
|
+
# plan = account.plan
|
|
11
|
+
# features = plan.features
|
|
12
|
+
# # ...
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# # good - declarative with methods
|
|
16
|
+
# def process
|
|
17
|
+
# apply_features
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def features
|
|
21
|
+
# plan.features
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def plan
|
|
25
|
+
# account.plan
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# def account
|
|
29
|
+
# user.account
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# def user
|
|
33
|
+
# User.find(user_id)
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# # good - attribute assignments don't count
|
|
37
|
+
# def configure
|
|
38
|
+
# config.timeout = 30
|
|
39
|
+
# config.retries = 3
|
|
40
|
+
# config.verbose = true
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
class RuboCop::Cop::Callbacksystems::TooManyLocalVariables < RuboCop::Cop::Callbacksystems::Base
|
|
44
|
+
def on_def(node)
|
|
45
|
+
report LocalVariableAssignments.new(node, max: cop_config["MaxAssignments"])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
alias on_defs on_def
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
class LocalVariableAssignments
|
|
52
|
+
include RuboCop::Callbacksystems::Helpers
|
|
53
|
+
|
|
54
|
+
MESSAGE = "Method has %<count>d local variable assignments (max %<max>d). " \
|
|
55
|
+
"Extract methods instead of using local variables."
|
|
56
|
+
|
|
57
|
+
def initialize(node, max:)
|
|
58
|
+
@node = node
|
|
59
|
+
@max = max
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def offense
|
|
63
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if count > max
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
attr_reader :node, :max
|
|
68
|
+
|
|
69
|
+
def count
|
|
70
|
+
@count ||= assignment_count(node.body, :lvasgn, :match_var)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def message
|
|
74
|
+
format(MESSAGE, count:, max:)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|