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,57 @@
|
|
|
1
|
+
# Constants are read, never mutated, so freezing one guards against a mistake
|
|
2
|
+
# nobody makes and puts noise on every declaration. Core `Style/MutableConstant`
|
|
3
|
+
# asks for the opposite and stays off, while `Style/RedundantFreeze` reports the
|
|
4
|
+
# same line whenever the value was immutable already.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad
|
|
8
|
+
# FORMATS = [ :json ].freeze
|
|
9
|
+
# TIMEOUT = "30s".freeze
|
|
10
|
+
#
|
|
11
|
+
# # good
|
|
12
|
+
# FORMATS = [ :json ]
|
|
13
|
+
# TIMEOUT = "30s"
|
|
14
|
+
#
|
|
15
|
+
class RuboCop::Cop::Callbacksystems::RedundantConstantFreeze < RuboCop::Cop::Callbacksystems::Base
|
|
16
|
+
extend RuboCop::Cop::AutoCorrector
|
|
17
|
+
|
|
18
|
+
MESSAGE = "Remove `freeze`. A constant is read, not mutated."
|
|
19
|
+
|
|
20
|
+
def on_casgn(node)
|
|
21
|
+
call = frozen_call_in(node)
|
|
22
|
+
|
|
23
|
+
report call if call
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
def frozen_call_in(node)
|
|
28
|
+
expression = node.children.last
|
|
29
|
+
expression = expression.children.first while parenthesized_expression?(expression)
|
|
30
|
+
expression if freezing_call?(expression)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parenthesized_expression?(node)
|
|
34
|
+
node.begin_type? && node.children.one?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def freezing_call?(node)
|
|
38
|
+
node.call_type? && node.receiver && freezes_without_arguments?(node)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def freezes_without_arguments?(node)
|
|
42
|
+
node.method?(:freeze) && node.arguments.empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def report(call)
|
|
46
|
+
range = freeze_range_of(call)
|
|
47
|
+
if source_comments.any_within?(range)
|
|
48
|
+
add_offense(range, message: MESSAGE)
|
|
49
|
+
else
|
|
50
|
+
add_offense(range, message: MESSAGE) { it.remove(range) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def freeze_range_of(call)
|
|
55
|
+
call.source_range.with(begin_pos: call.loc.dot.begin_pos)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# A constant reference does not need to repeat the namespace it is already
|
|
2
|
+
# written inside. The prefix is noise on every read, and it has to be rewritten
|
|
3
|
+
# whenever the namespace moves, while the bare name resolves through the scope
|
|
4
|
+
# the file already opens.
|
|
5
|
+
#
|
|
6
|
+
# Only the innermost lexical scope counts. Under the compact class style a
|
|
7
|
+
# definition like `class PgBox::Configuration` opens a single scope, so `PgBox`
|
|
8
|
+
# itself is never searched and its prefix has to stay.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad
|
|
12
|
+
# class PgBox::Configuration
|
|
13
|
+
# def validator
|
|
14
|
+
# PgBox::Configuration::Validator
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # good
|
|
19
|
+
# class PgBox::Configuration
|
|
20
|
+
# def validator
|
|
21
|
+
# Validator
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good - `PgBox` is not a lexical scope here, so the prefix is required
|
|
26
|
+
# class PgBox::Configuration
|
|
27
|
+
# def secrets
|
|
28
|
+
# PgBox::Secrets
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
class RuboCop::Cop::Callbacksystems::RedundantConstantNamespace < RuboCop::Cop::Callbacksystems::Base
|
|
33
|
+
include RuboCop::Callbacksystems::ProjectIndex::Support
|
|
34
|
+
extend RuboCop::Cop::AutoCorrector
|
|
35
|
+
|
|
36
|
+
def on_new_investigation
|
|
37
|
+
@constant_references = if project_index_reliable?
|
|
38
|
+
RuboCop::Callbacksystems::ProjectIndex::ConstantReferences.for(project_index).within(processed_source)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def on_const(node)
|
|
43
|
+
report ConstantReference.new(node, constant_references) if constant_references
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
attr_reader :constant_references
|
|
48
|
+
|
|
49
|
+
class ConstantReference
|
|
50
|
+
include RuboCop::Callbacksystems::Helpers
|
|
51
|
+
|
|
52
|
+
MESSAGE = "Remove the redundant `%<namespace>s::` prefix; the constant already resolves from here."
|
|
53
|
+
|
|
54
|
+
def initialize(node, constant_references)
|
|
55
|
+
@node = node
|
|
56
|
+
@constant_references = constant_references
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def offense
|
|
60
|
+
RuboCop::Callbacksystems::Offense.new(node, message) { correct(it) } if reference? && redundantly_qualified?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
attr_reader :node, :constant_references
|
|
65
|
+
|
|
66
|
+
def reference?
|
|
67
|
+
!node.parent&.const_type? && !definition_identifier?(node)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def redundantly_qualified?
|
|
71
|
+
enclosing_scope_name.present? && qualified_node.present? && explicitly_declared_scope? && same_declaration?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def enclosing_scope_name
|
|
75
|
+
@enclosing_scope_name ||= enclosing_definitions.map { constant_name_of(it.identifier) }.join("::")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def enclosing_definitions
|
|
79
|
+
node.each_ancestor(:class, :module).to_a.reverse
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def qualified_node
|
|
83
|
+
@qualified_node ||= constant_chain.find { constant_name_of(it.namespace) == enclosing_scope_name }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def constant_chain
|
|
87
|
+
[ node, *node.each_descendant(:const) ].select(&:namespace)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def explicitly_declared_scope?
|
|
91
|
+
enclosing_scope_chain.present? && enclosing_scope_chain.all? { declared_namespace?(it) } &&
|
|
92
|
+
enclosing_scope_chain.last.owner&.name == "Object"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def enclosing_scope_chain
|
|
96
|
+
@enclosing_scope_chain ||= Enumerator.produce(enclosing_scope_declaration) { it&.owner }
|
|
97
|
+
.take_while { it.present? && it.name != "Object" }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def enclosing_scope_declaration
|
|
101
|
+
constant_references.referenced_declaration_for(qualified_node.namespace)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def declared_namespace?(declaration)
|
|
105
|
+
declaration.is_a?(Rubydex::Namespace) && declared?(declaration)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def declared?(declaration)
|
|
109
|
+
declaration&.definitions&.any?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def same_declaration?
|
|
113
|
+
written_declaration = constant_references.referenced_declaration_for(node)
|
|
114
|
+
shortened_declaration = constant_references.declaration_named(shortened_name, beside: node)
|
|
115
|
+
|
|
116
|
+
declared?(written_declaration) && declared?(shortened_declaration) &&
|
|
117
|
+
written_declaration.name == shortened_declaration.name
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def shortened_name
|
|
121
|
+
chain = [ node ]
|
|
122
|
+
chain << chain.last.namespace until chain.last.equal?(qualified_node)
|
|
123
|
+
chain.reverse_each.map(&:short_name).join("::")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def message
|
|
127
|
+
format(MESSAGE, namespace: enclosing_scope_name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def correct(corrector)
|
|
131
|
+
corrector.remove(node.source_range.with(end_pos: qualified_node.loc.name.begin_pos))
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# A comment block wrapped to a narrower column than the project uses costs the
|
|
2
|
+
# reader extra lines for nothing. When a line breaks mid-sentence and the first
|
|
3
|
+
# word of the next line still fits under the Layout/LineLength Max, the block
|
|
4
|
+
# is refilled to the full width. A break on a sentence boundary may be
|
|
5
|
+
# deliberate, and a block carrying a list, an indented example, a code tail,
|
|
6
|
+
# a backtick fence, or an unbreakable word keeps the shape its author gave it.
|
|
7
|
+
# A line opening with a label (`Slot 1: ...`) or reading as code is an item of
|
|
8
|
+
# its own, so it keeps its line too.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad - the break falls mid-sentence with room left on the line
|
|
12
|
+
# # A comment that stops
|
|
13
|
+
# # mid-sentence.
|
|
14
|
+
#
|
|
15
|
+
# # good - filled to the full width
|
|
16
|
+
# # A comment that stops mid-sentence.
|
|
17
|
+
#
|
|
18
|
+
# # good - every break lands on a sentence boundary
|
|
19
|
+
# # First idea ends here.
|
|
20
|
+
# # Second idea starts here.
|
|
21
|
+
#
|
|
22
|
+
class RuboCop::Cop::Callbacksystems::ReflowComments < RuboCop::Cop::Callbacksystems::Base
|
|
23
|
+
extend RuboCop::Cop::AutoCorrector
|
|
24
|
+
|
|
25
|
+
def on_new_investigation
|
|
26
|
+
comment_blocks_in(processed_source, reject_tooling_runs: true).each do |comments|
|
|
27
|
+
report ProseBlock.new(comments, max_line_length), rewrites_comments: true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
class ProseBlock
|
|
33
|
+
include RuboCop::Callbacksystems::Helpers
|
|
34
|
+
|
|
35
|
+
MESSAGE = "This comment breaks mid-sentence with room left on the line. Fill it to %d columns."
|
|
36
|
+
MARKER_WIDTH = "# ".length
|
|
37
|
+
SENTENCE_END = /[.:;!?]["'`)\]]?\z/
|
|
38
|
+
STRUCTURE = /\A(?:\s|[-*|>+@#=]|\d+[.)]|:[a-z][\w-]*:|(?:--|\+\+)\z)/i
|
|
39
|
+
CODE_TAIL = /[{(=]\z/
|
|
40
|
+
LABELED = /\A[A-Za-z][^:\n]{0,30}:\s/
|
|
41
|
+
CODE = /\A[\w.]+(?:\(|\s+[A-Z]\w*::|\s+:\w|\s+["']|\s*=\s)/
|
|
42
|
+
|
|
43
|
+
def initialize(comments, max_length)
|
|
44
|
+
@comments = comments
|
|
45
|
+
@max_length = max_length
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def offense
|
|
49
|
+
RuboCop::Callbacksystems::Offense.new(range, message) { correct(it) } if narrow_wrap?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
attr_reader :comments, :max_length
|
|
54
|
+
|
|
55
|
+
def narrow_wrap?
|
|
56
|
+
prose? && lines.each_index.any? { absorbs_next?(it) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def prose?
|
|
60
|
+
lines.all? { prose_line?(it) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def lines
|
|
64
|
+
@lines ||= comments.map { it.text.delete_prefix("#").delete_prefix(" ").rstrip }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def prose_line?(line)
|
|
68
|
+
!line.match?(STRUCTURE) && !line.match?(CODE_TAIL) && line.exclude?("```") &&
|
|
69
|
+
line.split.all? { width_of(it) <= max_length }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def width_of(text)
|
|
73
|
+
indent + MARKER_WIDTH + text.length
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def indent
|
|
77
|
+
comments.first.source_range.column
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def absorbs_next?(index)
|
|
81
|
+
continues_at?(index) && width_of("#{lines[index]} #{lines[index + 1].split.first}") <= max_length
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# A line opening with a label or reading as code is an item of its own, not the tail of the sentence above it.
|
|
85
|
+
def continues_at?(index)
|
|
86
|
+
following = lines[index + 1]
|
|
87
|
+
following.present? && !lines[index].empty? && !lines[index].match?(SENTENCE_END) &&
|
|
88
|
+
!following.match?(LABELED) && !following.match?(CODE)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def range
|
|
92
|
+
range_spanning(comments)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def message
|
|
96
|
+
format(MESSAGE, max_length)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def correct(corrector)
|
|
100
|
+
corrector.replace(range, filled_text)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def filled_text
|
|
104
|
+
paragraphs.flat_map { filled(it) }.join("\n#{" " * indent}")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def paragraphs
|
|
108
|
+
lines.each_index.slice_when { |index, _| !continues_at?(index) }
|
|
109
|
+
.map { lines.values_at(*it) }
|
|
110
|
+
.flat_map { detached_blank(it) }
|
|
111
|
+
.reject(&:empty?)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def detached_blank(slice)
|
|
115
|
+
slice.last == "" ? [ slice[0...-1], [ "" ] ] : [ slice ]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def filled(paragraph)
|
|
119
|
+
words = paragraph.join(" ").split
|
|
120
|
+
words.any? ? wrapped(words).map { "# #{it}" } : [ "#" ]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def wrapped(words)
|
|
124
|
+
words.each_with_object([]) do |word, filled|
|
|
125
|
+
if filled.any? && width_of("#{filled.last} #{word}") <= max_length
|
|
126
|
+
filled[-1] = "#{filled.last} #{word}"
|
|
127
|
+
else
|
|
128
|
+
filled << word
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Detects a fixture called in several tests of one file. Each test then repeats
|
|
2
|
+
# the line naming the record, and a reader learns which record the file is
|
|
3
|
+
# about only by comparing them, where one `setup` says it once and every test
|
|
4
|
+
# reads it from there.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# # bad - same fixture in multiple tests
|
|
8
|
+
# test "validates name" do
|
|
9
|
+
# user = users(:john)
|
|
10
|
+
# assert user.valid?
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# test "validates email" do
|
|
14
|
+
# user = users(:john)
|
|
15
|
+
# assert user.email.present?
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # good - fixture in setup
|
|
19
|
+
# setup do
|
|
20
|
+
# @user = users(:john)
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# test "validates name" do
|
|
24
|
+
# assert @user.valid?
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# test "validates email" do
|
|
28
|
+
# assert @user.email.present?
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
class RuboCop::Cop::Callbacksystems::RepeatedFixtureInTests < RuboCop::Cop::Callbacksystems::Base
|
|
32
|
+
include RuboCop::Callbacksystems::Testing::CopHelpers
|
|
33
|
+
|
|
34
|
+
def external_dependency_checksum
|
|
35
|
+
RuboCop::Callbacksystems::Source::FilesChecksum.for \
|
|
36
|
+
"**/test/fixtures/**/*.yml", root: project_root, include_contents: false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def on_new_investigation
|
|
40
|
+
tests_by_class.each_value { report_each RepeatedFixtures.new(it, test_blocks:) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def tests_by_class
|
|
45
|
+
test_blocks.each_with_object({}.compare_by_identity) do |test, groups|
|
|
46
|
+
(groups[test_domain_of(test)] ||= []) << test
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_domain_of(test)
|
|
51
|
+
RuboCop::Callbacksystems::Methods::Domain.new(test).container || processed_source.ast
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class RepeatedFixtures
|
|
55
|
+
include RuboCop::Callbacksystems::Helpers
|
|
56
|
+
|
|
57
|
+
MESSAGE = "Fixture `%<fixture>s` is used in multiple tests. Move it to `setup`."
|
|
58
|
+
|
|
59
|
+
def initialize(tests, test_blocks:)
|
|
60
|
+
@tests = tests
|
|
61
|
+
@test_block_index = {}.compare_by_identity
|
|
62
|
+
test_blocks.each { @test_block_index[it] = true }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def each_offense
|
|
66
|
+
repeated_calls.each { yield RuboCop::Callbacksystems::Offense.new(it.node, message_for(it)) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
attr_reader :tests, :test_block_index
|
|
71
|
+
|
|
72
|
+
def repeated_calls
|
|
73
|
+
calls.group_by(&:identity).values.select { spans_several_tests?(it) }.flat_map { beyond_first_test(it) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def calls
|
|
77
|
+
tests.flat_map { calls_in(it) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def calls_in(test)
|
|
81
|
+
RuboCop::Callbacksystems::Execution::Immediate.new(test.body, deferred_blocks: nested_tests_in(test))
|
|
82
|
+
.nodes_of_type(:send)
|
|
83
|
+
.map { RuboCop::Callbacksystems::Testing::FixtureCall.new(it) }.select(&:valid?).map { Call.new(test, it) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def nested_tests_in(test)
|
|
87
|
+
nodes_in(test.body, :any_block).select { test_block_index.key?(it) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def spans_several_tests?(calls)
|
|
91
|
+
calls.map(&:test).uniq(&:object_id).many?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def beyond_first_test(calls)
|
|
95
|
+
calls.reject { it.test.equal?(calls.first.test) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def message_for(call)
|
|
99
|
+
format(MESSAGE, fixture: call.signature)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class Call < Data.define(:test, :fixture)
|
|
103
|
+
delegate :identity, :node, :signature, to: :fixture
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Restricts controller actions to the seven standard Rails actions. A custom
|
|
2
|
+
# action is a verb bolted onto a controller, so the controllers stop reading the
|
|
3
|
+
# same way and routes grow one-off entries. The verb turned into a noun gets a
|
|
4
|
+
# controller of its own, `Users::ActivationsController#create`, with the same
|
|
5
|
+
# shape as every other one.
|
|
6
|
+
#
|
|
7
|
+
# Only classes inheriting from a controller are read. A concern is a module,
|
|
8
|
+
# and its methods are not actions.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad - custom public action
|
|
12
|
+
# class UsersController < ApplicationController
|
|
13
|
+
# def activate
|
|
14
|
+
# # ...
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # good - use standard actions
|
|
19
|
+
# class UsersController < ApplicationController
|
|
20
|
+
# def update
|
|
21
|
+
# # ...
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good - create a new controller for the action
|
|
26
|
+
# class Users::ActivationsController < ApplicationController
|
|
27
|
+
# def create
|
|
28
|
+
# # ...
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# # good - concerns can have any methods
|
|
33
|
+
# module Authentication
|
|
34
|
+
# def current_user
|
|
35
|
+
# # ...
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
class RuboCop::Cop::Callbacksystems::RestrictedControllerActions < RuboCop::Cop::Callbacksystems::Base
|
|
40
|
+
def on_def(node)
|
|
41
|
+
report Action.new(node)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
class Action
|
|
46
|
+
include RuboCop::Callbacksystems::Helpers
|
|
47
|
+
|
|
48
|
+
MESSAGE = "Only standard Rails actions (index, show, new, create, edit, update, destroy) are allowed in " \
|
|
49
|
+
"controllers. Extract `%<method>s` to a new controller."
|
|
50
|
+
|
|
51
|
+
def initialize(node)
|
|
52
|
+
@node = node
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def offense
|
|
56
|
+
RuboCop::Callbacksystems::Offense.new(node, message) if restricted?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
attr_reader :node
|
|
61
|
+
|
|
62
|
+
def restricted?
|
|
63
|
+
custom? && direct_method_definition?(node) && instance_method? && in_restricted_controller? &&
|
|
64
|
+
public_method?(node)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def custom?
|
|
68
|
+
STANDARD_CONTROLLER_ACTIONS.exclude?(node.method_name)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def instance_method?
|
|
72
|
+
RuboCop::Callbacksystems::Methods::Domain.new(node).scope == :instance
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def in_restricted_controller?
|
|
76
|
+
enclosing_class_or_module_of(node).then do |controller|
|
|
77
|
+
controller_class?(controller) && !application_controller_class?(controller)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def message
|
|
82
|
+
format(MESSAGE, method: node.method_name)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|