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,168 @@
|
|
|
1
|
+
# Enforces an empty line before method definitions. The empty line is what
|
|
2
|
+
# separates one definition from the next when reading down a class, so a method
|
|
3
|
+
# pressed against a macro or the method above reads as a continuation of it.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# # bad - macro directly before method
|
|
7
|
+
# before_action :authenticate
|
|
8
|
+
# def index
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# # bad - consecutive methods without empty line
|
|
12
|
+
# def name
|
|
13
|
+
# end
|
|
14
|
+
# def email
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# # good - empty line before method
|
|
18
|
+
# before_action :authenticate
|
|
19
|
+
#
|
|
20
|
+
# def index
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# # good - empty line between methods
|
|
24
|
+
# def name
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# def email
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # good - consecutive macros are fine
|
|
31
|
+
# validates :name
|
|
32
|
+
# validates :email
|
|
33
|
+
#
|
|
34
|
+
# # good - first method in class body needs no preceding empty line
|
|
35
|
+
# class User
|
|
36
|
+
# def name
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# # good - method after visibility modifier needs no empty line
|
|
41
|
+
# private
|
|
42
|
+
# def helper
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
class RuboCop::Cop::Callbacksystems::EmptyLineBeforeMethod < RuboCop::Cop::Callbacksystems::Base
|
|
46
|
+
extend RuboCop::Cop::AutoCorrector
|
|
47
|
+
|
|
48
|
+
def on_def(node)
|
|
49
|
+
report MethodDefinition.new(node, describing_macros)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
alias on_defs on_def
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
def describing_macros
|
|
56
|
+
Array(cop_config["DescribingMacros"]).map(&:to_sym)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class MethodDefinition
|
|
60
|
+
include RuboCop::Callbacksystems::Helpers
|
|
61
|
+
|
|
62
|
+
MESSAGE = "Add an empty line before `%<method>s`."
|
|
63
|
+
|
|
64
|
+
def initialize(node, describing_macros)
|
|
65
|
+
@node = node
|
|
66
|
+
@describing_macros = describing_macros
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def offense
|
|
70
|
+
if crowded?
|
|
71
|
+
RuboCop::Callbacksystems::Offense.new(node, message, correcting: source_ordered?) { correct(it) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
attr_reader :node, :describing_macros
|
|
77
|
+
delegate :adjacent_to_previous?, :leading_node, :previous_node, to: :description_group, private: true
|
|
78
|
+
|
|
79
|
+
def description_group
|
|
80
|
+
@description_group ||= DescriptionGroup.new(node, describing_macros)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def crowded?
|
|
84
|
+
previous_node && !after_visibility_modifier? && missing_empty_line?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def after_visibility_modifier?
|
|
88
|
+
!visibility_modifier_of(previous_node).nil?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def missing_empty_line?
|
|
92
|
+
adjacent_to_previous?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def message
|
|
96
|
+
format(MESSAGE, method: node.method_name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def source_ordered?
|
|
100
|
+
range_through_heredocs(previous_node).end_pos <= leading_node.source_range.begin_pos
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def correct(corrector)
|
|
104
|
+
if previous_node.last_line == leading_node.first_line
|
|
105
|
+
corrector.replace \
|
|
106
|
+
previous_node.source_range.end.join(leading_node.source_range.begin),
|
|
107
|
+
"\n\n#{indentation_of(previous_node)}"
|
|
108
|
+
else
|
|
109
|
+
corrector.insert_before(line_start_of(leading_node), "\n")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def line_start_of(statement)
|
|
114
|
+
position = line_start_position_of(statement)
|
|
115
|
+
statement.source_range.with(begin_pos: position, end_pos: position)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The consecutive describing macros attached to a method and the statement immediately before that group.
|
|
119
|
+
class DescriptionGroup
|
|
120
|
+
include RuboCop::Callbacksystems::Helpers
|
|
121
|
+
|
|
122
|
+
def initialize(node, describing_macros)
|
|
123
|
+
@node = node
|
|
124
|
+
@describing_macros = describing_macros
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def adjacent_to_previous?
|
|
128
|
+
previous_node && adjacent?(above: previous_node, below: leading_node)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def previous_node
|
|
132
|
+
siblings[leading_position.pred] if leading_position.positive?
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def leading_node
|
|
136
|
+
siblings.fetch(leading_position)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
attr_reader :node, :describing_macros
|
|
141
|
+
|
|
142
|
+
def leading_position
|
|
143
|
+
@leading_position ||= node_position.downto(1).find { !joins_description_at?(it) } || 0
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def node_position
|
|
147
|
+
@node_position ||= siblings.index(node)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def siblings
|
|
151
|
+
@siblings ||= node.parent&.begin_type? ? node.parent.child_nodes : [ node ]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def joins_description_at?(position)
|
|
155
|
+
above = siblings.fetch(position.pred)
|
|
156
|
+
describing?(above) && adjacent?(above:, below: siblings.fetch(position))
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def describing?(statement)
|
|
160
|
+
bare_send?(statement) && describing_macros.include?(statement.method_name)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def adjacent?(above:, below:)
|
|
164
|
+
(below.first_line - range_through_heredocs(above).last_line) <= 1
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# A class body reads in sections: the mixins, the constants, the declarations of its state, the macros, the methods,
|
|
2
|
+
# and the nested classes. A blank line is what tells the reader one section has ended and the next begins, so two
|
|
3
|
+
# adjacent statements of different sections need one between them, while the statements of one section sit together
|
|
4
|
+
# or apart as the author likes. `BodyOrder` owns the order the sections come in, and this cop asks only for the blank
|
|
5
|
+
# line, whichever way round the two statements sit.
|
|
6
|
+
#
|
|
7
|
+
# The sections are coarser than the ranks `BodyOrder` reads. An `attribute`, an association and a `delegate` all
|
|
8
|
+
# declare what the object holds, so they are one section, the way the authentication generator writes
|
|
9
|
+
# `attribute :session` right above `delegate :user, ...` and `has_secure_password` right above `has_many :sessions`.
|
|
10
|
+
#
|
|
11
|
+
# A visibility modifier belongs to no section, so the pair it makes with the statement on either side is left to the
|
|
12
|
+
# Layout cops.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# # bad - the token sits pressed against the constant
|
|
16
|
+
# class Key < ApplicationRecord
|
|
17
|
+
# PREFIX = "key_"
|
|
18
|
+
# has_secure_token :value
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# # good
|
|
22
|
+
# class Key < ApplicationRecord
|
|
23
|
+
# PREFIX = "key_"
|
|
24
|
+
#
|
|
25
|
+
# has_secure_token :value
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# # good - the state declarations are one section
|
|
29
|
+
# class Current < ActiveSupport::CurrentAttributes
|
|
30
|
+
# attribute :session
|
|
31
|
+
# delegate :user, to: :session, allow_nil: true
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
class RuboCop::Cop::Callbacksystems::EmptyLineBetweenSections < RuboCop::Cop::Callbacksystems::Base
|
|
35
|
+
extend RuboCop::Cop::AutoCorrector
|
|
36
|
+
|
|
37
|
+
def on_class(node)
|
|
38
|
+
report_each Body.new(node.body, processed_source)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
alias on_module on_class
|
|
42
|
+
alias on_sclass on_class
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
class Body
|
|
46
|
+
include RuboCop::Callbacksystems::Helpers
|
|
47
|
+
|
|
48
|
+
def initialize(body, processed_source)
|
|
49
|
+
@body = body
|
|
50
|
+
@processed_source = processed_source
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def each_offense(&block)
|
|
54
|
+
statements.each_cons(2).filter_map { |above, below| Boundary.new(above:, below:).offense }.each(&block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
attr_reader :body, :processed_source
|
|
59
|
+
|
|
60
|
+
def statements
|
|
61
|
+
@statements ||= nodes.map { Statement.new(it, nodes, processed_source) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def nodes
|
|
65
|
+
@nodes ||= statements_in(body)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Two statements written one after the other, read for the sections they belong to.
|
|
70
|
+
class Boundary
|
|
71
|
+
include RuboCop::Callbacksystems::Helpers
|
|
72
|
+
|
|
73
|
+
MESSAGE = "Separate `%<statement>s` from the %<above>s above with a blank line, since the %<below>s are " \
|
|
74
|
+
"another section."
|
|
75
|
+
|
|
76
|
+
def initialize(above:, below:)
|
|
77
|
+
@above = above
|
|
78
|
+
@below = below
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def offense
|
|
82
|
+
if crowded?
|
|
83
|
+
RuboCop::Callbacksystems::Offense.new(below.node, message, correcting: source_ordered?) { correct(it) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
attr_reader :above, :below
|
|
89
|
+
|
|
90
|
+
def crowded?
|
|
91
|
+
crosses_sections? && !below.separated_from?(above)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def crosses_sections?
|
|
95
|
+
[ above, below ].none?(&:visibility_modifier?) && above.section != below.section
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def message
|
|
99
|
+
format(MESSAGE, statement: below.opening_line, above: above.section, below: below.section)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def source_ordered?
|
|
103
|
+
above.last_line <= below.node.first_line
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def correct(corrector)
|
|
107
|
+
if above.last_line == below.node.first_line
|
|
108
|
+
corrector.replace \
|
|
109
|
+
above.node.source_range.end.join(below.node.source_range.begin),
|
|
110
|
+
"\n\n#{indentation_of(above.node)}"
|
|
111
|
+
else
|
|
112
|
+
corrector.insert_before(below.line_start, "\n")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class Statement
|
|
118
|
+
include RuboCop::Callbacksystems::Helpers
|
|
119
|
+
|
|
120
|
+
SECTIONS = {
|
|
121
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::MIXIN => "mixins",
|
|
122
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::VALUE => "constants",
|
|
123
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::CLASS_DECLARATION => "constants",
|
|
124
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::ATTRIBUTE => "declarations",
|
|
125
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::ASSOCIATION => "declarations",
|
|
126
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::DELEGATE => "declarations",
|
|
127
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::MACRO => "macros",
|
|
128
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::METHOD => "methods",
|
|
129
|
+
RuboCop::Callbacksystems::ClassStructure::StatementRank::NESTED_CLASS => "nested classes"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
attr_reader :node
|
|
133
|
+
|
|
134
|
+
def initialize(node, siblings, processed_source)
|
|
135
|
+
@node = node
|
|
136
|
+
@siblings = siblings
|
|
137
|
+
@processed_source = processed_source
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def visibility_modifier?
|
|
141
|
+
visibility_modifier_of(node).present?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def section
|
|
145
|
+
@section ||= SECTIONS.fetch(RuboCop::Callbacksystems::ClassStructure::StatementRank.new(node, siblings).to_i)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def separated_from?(above)
|
|
149
|
+
processed_source.lines[above.last_line...(node.first_line - 1)].any?(&:blank?)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def last_line
|
|
153
|
+
range_through_heredocs(node).last_line
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def opening_line
|
|
157
|
+
node.source.lines.first.strip
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The blank line goes above the comments introducing the statement, which travel with it.
|
|
161
|
+
def line_start
|
|
162
|
+
position = RuboCop::Callbacksystems::Source::StatementWithComments.new(node, processed_source).begin_position
|
|
163
|
+
node.source_range.with(begin_pos: position, end_pos: position)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
attr_reader :siblings, :processed_source
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# A `private` keyword with nothing after it declares a section that holds
|
|
2
|
+
# nothing. Remove it and let the class end where it ends.
|
|
3
|
+
#
|
|
4
|
+
# The correction stays away when another expression observes the value of the
|
|
5
|
+
# class or module definition: `private` returns `nil`, while the statement above
|
|
6
|
+
# it may return something else.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # bad
|
|
10
|
+
# class Order
|
|
11
|
+
# def total
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# private
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# # good
|
|
18
|
+
# class Order
|
|
19
|
+
# def total
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
class RuboCop::Cop::Callbacksystems::EmptyPrivateSection < RuboCop::Cop::Callbacksystems::Base
|
|
24
|
+
extend RuboCop::Cop::AutoCorrector
|
|
25
|
+
|
|
26
|
+
def on_send(node)
|
|
27
|
+
report PrivateSection.new(node, trailing_comment: trailing_comment_on(node))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias on_csend on_send
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
def trailing_comment_on(node)
|
|
34
|
+
RuboCop::Callbacksystems::Source::Comments.for(processed_source).trailing_comment_on(node.last_line)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class PrivateSection
|
|
38
|
+
include RuboCop::Callbacksystems::Helpers
|
|
39
|
+
|
|
40
|
+
MESSAGE = "Remove the empty `private` section."
|
|
41
|
+
|
|
42
|
+
def initialize(node, trailing_comment:)
|
|
43
|
+
@node = node
|
|
44
|
+
@trailing_comment = trailing_comment
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def offense
|
|
48
|
+
if empty?
|
|
49
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE, correcting: correctable?) do |corrector|
|
|
50
|
+
correct(corrector)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
attr_reader :node, :trailing_comment
|
|
57
|
+
|
|
58
|
+
def empty?
|
|
59
|
+
private_modifier? && closes_enclosing_body?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def private_modifier?
|
|
63
|
+
visibility_modifier_of(node) == :private
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def closes_enclosing_body?
|
|
67
|
+
statements_in(enclosing_body).last.equal?(node)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# `enclosing_body_for` reports only multi-statement bodies, and a body of just the modifier has one statement.
|
|
71
|
+
def enclosing_body
|
|
72
|
+
enclosing_definition_of(node)&.body
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def correctable?
|
|
76
|
+
definition_result_discarded? && !moves_tooling_comment?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def definition_result_discarded?
|
|
80
|
+
RuboCop::Callbacksystems::Execution::DiscardedExpression.new(enclosing_definition_of(node)).discarded?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def moves_tooling_comment?
|
|
84
|
+
trailing_comment && tooling_comment?(trailing_comment)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def correct(corrector)
|
|
88
|
+
if inline_removal_range && trailing_comment
|
|
89
|
+
corrector.replace(inline_removal_range.join(trailing_comment.source_range), standing_comment)
|
|
90
|
+
else
|
|
91
|
+
corrector.remove(statement_removal_range_for(node))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def inline_removal_range
|
|
96
|
+
@inline_removal_range ||= inline_statement_removal_range_for(node)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def standing_comment
|
|
100
|
+
"\n#{line_indentation}#{trailing_comment.text}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def line_indentation
|
|
104
|
+
node.source_range.source_line[/\A[ \t]*/]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# An empty class or module keeps its `end` on a line of its own, the way
|
|
2
|
+
# `Style/EmptyMethod` already asks of an empty method.
|
|
3
|
+
#
|
|
4
|
+
# A nested empty class is left to `PreferClassNewForEmptyNestedClass`, which
|
|
5
|
+
# turns it into a constant rather than a definition.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# # bad
|
|
9
|
+
# class PgBox::ConfigurationError < StandardError; end
|
|
10
|
+
#
|
|
11
|
+
# # good
|
|
12
|
+
# class PgBox::ConfigurationError < StandardError
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
class RuboCop::Cop::Callbacksystems::ExpandedEmptyClassOrModule < RuboCop::Cop::Callbacksystems::Base
|
|
16
|
+
extend RuboCop::Cop::AutoCorrector
|
|
17
|
+
|
|
18
|
+
def on_class(node)
|
|
19
|
+
report SingleLineDefinition.new(node)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
alias on_module on_class
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
class SingleLineDefinition
|
|
26
|
+
include RuboCop::Callbacksystems::Helpers
|
|
27
|
+
|
|
28
|
+
MESSAGE = "Put `end` on its own line."
|
|
29
|
+
|
|
30
|
+
def initialize(node)
|
|
31
|
+
@node = node
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def offense
|
|
35
|
+
if expandable?
|
|
36
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE, correcting: superclass_without_heredoc?) { correct(it) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
attr_reader :node
|
|
42
|
+
|
|
43
|
+
def expandable?
|
|
44
|
+
node.body.nil? && node.single_line? && stays_a_definition?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def stays_a_definition?
|
|
48
|
+
node.module_type? || enclosing_class_or_module_of(node).nil?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def superclass_without_heredoc?
|
|
52
|
+
node.module_type? || node.parent_class.nil? || !carries_heredoc?(node.parent_class)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def correct(corrector)
|
|
56
|
+
corrector.replace(gap_before_end, "\n#{indentation_of(node)}")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def gap_before_end
|
|
60
|
+
node.source_range.with(begin_pos: header_end, end_pos: node.loc.end.begin_pos)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def header_end
|
|
64
|
+
(superclass || node.identifier).source_range.end_pos
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def superclass
|
|
68
|
+
node.parent_class if node.class_type?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Detects memoization methods that delegate to another method in the same class.
|
|
2
|
+
# The second method exists only to serve the first, so the reading is split
|
|
3
|
+
# across two definitions for nothing, and the computation belongs in the method
|
|
4
|
+
# that caches it.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - delegates to another method
|
|
8
|
+
# def user
|
|
9
|
+
# @user ||= find_user
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# private
|
|
13
|
+
# def find_user
|
|
14
|
+
# User.find(params[:id])
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# # good - computation is inline
|
|
18
|
+
# def user
|
|
19
|
+
# @user ||= User.find(params[:id])
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# # good - calls a method on a receiver (not delegation)
|
|
23
|
+
# def owner
|
|
24
|
+
# @owner ||= account.owner
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# # good - calls method with arguments
|
|
28
|
+
# def formatted_name
|
|
29
|
+
# @formatted_name ||= format_name(first, last)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# # good - has additional logic
|
|
33
|
+
# def user
|
|
34
|
+
# @user ||= find_user || default_user
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
class RuboCop::Cop::Callbacksystems::InlineMemoizedComputation < RuboCop::Cop::Callbacksystems::Base
|
|
38
|
+
def on_new_investigation
|
|
39
|
+
@method_calls = RuboCop::Callbacksystems::Methods::Calls.new(processed_source.ast)
|
|
40
|
+
@sibling_methods = RuboCop::Callbacksystems::Methods::Siblings.new
|
|
41
|
+
@macro_referenced_methods = RuboCop::Callbacksystems::Methods::MacroReferences::ContainerIndex.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def on_def(node)
|
|
45
|
+
report Memoization.new(node, method_calls:, sibling_methods:, macro_referenced_methods:)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
alias on_defs on_def
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
attr_reader :macro_referenced_methods, :method_calls, :sibling_methods
|
|
52
|
+
|
|
53
|
+
class Memoization
|
|
54
|
+
include RuboCop::Callbacksystems::Helpers
|
|
55
|
+
|
|
56
|
+
MESSAGE = "Inline the computation from `%<method>s` instead of delegating."
|
|
57
|
+
|
|
58
|
+
def initialize(node, method_calls:, sibling_methods:, macro_referenced_methods:)
|
|
59
|
+
@node = node
|
|
60
|
+
@method_calls = method_calls
|
|
61
|
+
@sibling_methods = sibling_methods
|
|
62
|
+
@macro_referenced_methods = macro_referenced_methods
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def offense
|
|
66
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if delegates_to_own_method?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
attr_reader :node, :method_calls, :sibling_methods, :macro_referenced_methods
|
|
71
|
+
delegate :expression, to: "node.body", private: true
|
|
72
|
+
|
|
73
|
+
def delegates_to_own_method?
|
|
74
|
+
node.body&.or_asgn_type? && delegating_call? && private_delegated_definition? && sole_call_site? &&
|
|
75
|
+
!macro_referenced?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def delegating_call?
|
|
79
|
+
bare_send?(expression) && expression.arguments.empty?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def private_delegated_definition?
|
|
83
|
+
delegated_definition && private_method?(delegated_definition)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def delegated_definition
|
|
87
|
+
@delegated_definition ||= sibling_methods.named(delegated_method, beside: node).first
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def delegated_method
|
|
91
|
+
expression.method_name
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def sole_call_site?
|
|
95
|
+
calls_to_delegated_method.one? && calls_to_delegated_method.first.equal?(expression)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def calls_to_delegated_method
|
|
99
|
+
@calls_to_delegated_method ||= method_calls.named(delegated_method, from: node)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def macro_referenced?
|
|
103
|
+
macro_referenced_methods.include?(delegated_method, beside: node)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def message
|
|
107
|
+
format(MESSAGE, method: delegated_method)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|