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,309 @@
|
|
|
1
|
+
# A hash literal of several fields, built in one place and read from several
|
|
2
|
+
# others, is a concept with no behavior: every caller reaches past it into its
|
|
3
|
+
# fields because it has nothing of its own. That is a data clump wearing braces,
|
|
4
|
+
# and the same refactor applies. Give it a class, and the methods reading its
|
|
5
|
+
# fields become its methods.
|
|
6
|
+
#
|
|
7
|
+
# What matters is whether the record travels. One built and consumed on the spot,
|
|
8
|
+
# an options argument or a payload passed straight to a call, is a return value
|
|
9
|
+
# rather than a concept, so only a record that is returned or assigned counts.
|
|
10
|
+
#
|
|
11
|
+
# A read counts for a record only when it goes through where the record lives:
|
|
12
|
+
# the constant or variable it was assigned to, the method returning it, or a
|
|
13
|
+
# local a method assigned from one of those. Sharing a few key names proves
|
|
14
|
+
# nothing, so `entry[:id]` on a form entry never counts against an unrelated
|
|
15
|
+
# hash that also carries an `id`.
|
|
16
|
+
#
|
|
17
|
+
# A hash returned from `as_json`, `to_h`, `to_hash` or `serializable_hash` is
|
|
18
|
+
# the wire format rather than a concept, so it is never a record.
|
|
19
|
+
#
|
|
20
|
+
# `DataClump` sees the same smell spelled as parameter lists. Bagging a clump into
|
|
21
|
+
# a hash silences that cop without changing anything, so this one closes the gap.
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# # bad - three fields and no behavior, read across methods
|
|
25
|
+
# SHAPE = { open: "{", close: "}", items: :children }
|
|
26
|
+
#
|
|
27
|
+
# def opening?
|
|
28
|
+
# source == SHAPE[:open]
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# def items
|
|
32
|
+
# node.public_send(SHAPE[:items])
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# # good - the record becomes a class and the readers become its methods
|
|
36
|
+
# class Shape
|
|
37
|
+
# def initialize(open, close, items)
|
|
38
|
+
# @open, @close, @items = open, close, items
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# def opening?(source)
|
|
42
|
+
# source == open
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# # good - built and consumed on the spot
|
|
47
|
+
# render json: { status: "ok", id: id, name: name }
|
|
48
|
+
#
|
|
49
|
+
class RuboCop::Cop::Callbacksystems::NoAnemicRecord < RuboCop::Cop::Callbacksystems::Base
|
|
50
|
+
def on_new_investigation
|
|
51
|
+
report_each Records.new \
|
|
52
|
+
processed_source.ast,
|
|
53
|
+
min_fields: cop_config["MinFields"],
|
|
54
|
+
min_readers: cop_config["MinReaders"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
Home = Data.define(:kind, :name, :domain)
|
|
59
|
+
|
|
60
|
+
# The traveling hash literals of a file, each with the reads that go through its home.
|
|
61
|
+
class Records
|
|
62
|
+
include RuboCop::Callbacksystems::Helpers
|
|
63
|
+
|
|
64
|
+
def initialize(ast, min_fields:, min_readers:)
|
|
65
|
+
@ast = ast
|
|
66
|
+
@min_fields = min_fields
|
|
67
|
+
@min_readers = min_readers
|
|
68
|
+
@local_variables = RuboCop::Callbacksystems::Execution::LocalVariableOccurrences.new(ast)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def each_offense
|
|
72
|
+
reached.select { it.readers >= min_readers }.each { yield it.offense }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
attr_reader :ast, :min_fields, :min_readers, :local_variables
|
|
77
|
+
delegate :reading_methods_of, to: :read_index, private: true
|
|
78
|
+
|
|
79
|
+
def read_index
|
|
80
|
+
@read_index ||= ReadIndex.new(nodes_in(ast, :send, :csend).map { Read.new(it, local_variables:) })
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def reached
|
|
84
|
+
candidates.map { ReachedRecord.new(it, reading_methods_of(it)) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def candidates
|
|
88
|
+
nodes_in(ast, :hash).map { Record.new(it) }.select { it.travels? && it.fields.size >= min_fields }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Field reads grouped by the object carrying them, so each record visits only its own reads.
|
|
93
|
+
class ReadIndex
|
|
94
|
+
def initialize(reads)
|
|
95
|
+
@by_container = {}.compare_by_identity
|
|
96
|
+
reads.each { add(it) if it.field && it.home }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def reading_methods_of(record)
|
|
100
|
+
reads_for(record).filter_map { it.reader if record.fields.include?(it.field) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
attr_reader :by_container
|
|
105
|
+
|
|
106
|
+
def add(read)
|
|
107
|
+
homes = by_container[read.container] ||= {}
|
|
108
|
+
(homes[read.home] ||= []) << read
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def reads_for(record)
|
|
112
|
+
by_container.fetch(record.container) { {} }.fetch(record.home) { [] }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
class Read
|
|
117
|
+
include RuboCop::Callbacksystems::Helpers
|
|
118
|
+
|
|
119
|
+
READING_METHODS = %i[ [] fetch dig ]
|
|
120
|
+
|
|
121
|
+
def initialize(node, local_variables:)
|
|
122
|
+
@node = node
|
|
123
|
+
@local_variables = local_variables
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def field
|
|
127
|
+
@field ||= node.first_argument.value if reads_a_field?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The name the read goes through, which is where the record it reaches has to live.
|
|
131
|
+
def home
|
|
132
|
+
@home ||= home_of(node.receiver)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def reader
|
|
136
|
+
enclosing_method_of(node)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def container
|
|
140
|
+
@container ||= domain.container
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
attr_reader :node, :local_variables
|
|
145
|
+
|
|
146
|
+
def reads_a_field?
|
|
147
|
+
READING_METHODS.include?(node.method_name) && single_symbol_argument?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def single_symbol_argument?
|
|
151
|
+
node.arguments.one? && node.first_argument.sym_type?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def home_of(expression)
|
|
155
|
+
case expression&.type
|
|
156
|
+
when :const then Home.new(:constant, constant_name_of(expression), nil)
|
|
157
|
+
when :ivar then Home.new(:instance_variable, expression.name, method_domain)
|
|
158
|
+
when :cvar, :gvar then Home.new(expression.type, expression.name, nil)
|
|
159
|
+
when :send, :csend then method_home_of(expression)
|
|
160
|
+
when :lvar then home_of(Local.new(expression, occurrences: local_variables).origin)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def method_domain
|
|
165
|
+
domain.identity
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def domain
|
|
169
|
+
@domain ||= RuboCop::Callbacksystems::Methods::Domain.new(node)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def method_home_of(expression)
|
|
173
|
+
Home.new(:method, expression.method_name, method_domain) if bare_send?(expression) ||
|
|
174
|
+
expression.receiver.self_type?
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# A local stands for the value its method assigned it once, unless that value is another local.
|
|
179
|
+
class Local
|
|
180
|
+
include RuboCop::Callbacksystems::Helpers
|
|
181
|
+
|
|
182
|
+
def initialize(variable, occurrences:)
|
|
183
|
+
@variable = variable
|
|
184
|
+
@occurrences = occurrences
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def origin
|
|
188
|
+
expression unless expression&.lvar_type?
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
attr_reader :variable, :occurrences
|
|
193
|
+
|
|
194
|
+
def expression
|
|
195
|
+
assignment&.expression
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def assignment
|
|
199
|
+
assignments.first if assignments.one? && assignments.first.lvasgn_type?
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def assignments
|
|
203
|
+
occurrences.named_in_method(variable.name, around: variable).select { it.type?(:lvasgn, :match_var) }
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class ReachedRecord
|
|
208
|
+
MESSAGE = "This record carries [%<fields>s] and no behavior, and %<readers>d methods reach into its fields. " \
|
|
209
|
+
"Give it a class and let those become its methods."
|
|
210
|
+
|
|
211
|
+
def initialize(record, reading_methods)
|
|
212
|
+
@record = record
|
|
213
|
+
@reading_methods = reading_methods
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def offense
|
|
217
|
+
RuboCop::Callbacksystems::Offense.new(record.node, message)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def readers
|
|
221
|
+
reading_methods.uniq.size
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private
|
|
225
|
+
attr_reader :record, :reading_methods
|
|
226
|
+
|
|
227
|
+
def message
|
|
228
|
+
format(MESSAGE, fields: record.fields.to_a.join(", "), readers: readers)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class Record
|
|
233
|
+
include RuboCop::Callbacksystems::Helpers
|
|
234
|
+
|
|
235
|
+
SERIALIZATION_METHODS = %i[ as_json to_h to_hash serializable_hash ]
|
|
236
|
+
VARIABLE_READ_TYPES = { cvasgn: :cvar, gvasgn: :gvar }
|
|
237
|
+
|
|
238
|
+
attr_reader :node
|
|
239
|
+
|
|
240
|
+
def initialize(node)
|
|
241
|
+
@node = node
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def travels?
|
|
245
|
+
symbol_keyed? && home.present? && !serialization_output?
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def home
|
|
249
|
+
@home ||= assigned_home || returning_method_home
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def fields
|
|
253
|
+
@fields ||= node.pairs.to_set { it.key.value }
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def container
|
|
257
|
+
@container ||= domain.container
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
private
|
|
261
|
+
def symbol_keyed?
|
|
262
|
+
node.pairs.any? && node.pairs.all? { it.key.sym_type? }
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def assigned_home
|
|
266
|
+
case node.parent&.type
|
|
267
|
+
when :casgn then Home.new(:constant, constant_assignment_name, nil)
|
|
268
|
+
when :ivasgn then Home.new(:instance_variable, node.parent.name, method_domain)
|
|
269
|
+
when :cvasgn, :gvasgn then Home.new(VARIABLE_READ_TYPES.fetch(node.parent.type), node.parent.name, nil)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def constant_assignment_name
|
|
274
|
+
[ constant_name_of(node.parent.children.first), node.parent.name ].compact.join("::")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def method_domain
|
|
278
|
+
domain.identity
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def domain
|
|
282
|
+
@domain ||= RuboCop::Callbacksystems::Methods::Domain.new(node)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def returning_method_home
|
|
286
|
+
Home.new(:method, returning_method_name, method_domain) if returning_method_name
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def returning_method_name
|
|
290
|
+
enclosing_method.method_name if returned?
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def returned?
|
|
294
|
+
enclosing_method.present? && (node.parent.return_type? || closes_its_method?)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def enclosing_method
|
|
298
|
+
@enclosing_method ||= enclosing_method_of(node)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def closes_its_method?
|
|
302
|
+
last_statement_in(enclosing_method.body).equal?(node)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def serialization_output?
|
|
306
|
+
SERIALIZATION_METHODS.include?(returning_method_name)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Prohibits using assert_select in tests.
|
|
2
|
+
#
|
|
3
|
+
# Testing HTML content couples tests to markup implementation details.
|
|
4
|
+
# Tests should verify behavior through response status, redirects, and
|
|
5
|
+
# data changes, not by inspecting HTML structure.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad - testing HTML content
|
|
9
|
+
# assert_select "div.notice", text: /Welcome/
|
|
10
|
+
# assert_select "h1", "Dashboard"
|
|
11
|
+
# assert_select "input[type=email]"
|
|
12
|
+
#
|
|
13
|
+
# # good - test response and behavior
|
|
14
|
+
# assert_response :success
|
|
15
|
+
# assert_redirected_to dashboard_path
|
|
16
|
+
# assert user.confirmed?
|
|
17
|
+
#
|
|
18
|
+
class RuboCop::Cop::Callbacksystems::NoAssertSelect < RuboCop::Cop::Callbacksystems::Base
|
|
19
|
+
MESSAGE = "Don't use `assert_select` to test HTML content. Test response status, redirects, and data changes instead."
|
|
20
|
+
|
|
21
|
+
# @!method assert_select_call?(node)
|
|
22
|
+
def_node_matcher :assert_select_call?, <<~PATTERN
|
|
23
|
+
(call {nil? (self)} :assert_select ...)
|
|
24
|
+
PATTERN
|
|
25
|
+
|
|
26
|
+
def on_send(node)
|
|
27
|
+
add_offense(node, message: MESSAGE) if assert_select_call?(node)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias on_csend on_send
|
|
31
|
+
end
|