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,286 @@
|
|
|
1
|
+
# A class whose whole body is class-level code should be a module with
|
|
2
|
+
# `extend self`. Classes are for making instances.
|
|
3
|
+
#
|
|
4
|
+
# `Style/StaticClass` covers the plain case, but it abstains as soon as the
|
|
5
|
+
# singleton section has a private part, because the `module_function` it
|
|
6
|
+
# corrects to stops copying methods to the singleton once `private` switches
|
|
7
|
+
# the mode, which breaks every call to them. `extend self` carries the
|
|
8
|
+
# visibility over untouched, so that case is reported here instead.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# # bad - class methods only, with a private helper
|
|
12
|
+
# class Architecture
|
|
13
|
+
# class << self
|
|
14
|
+
# def resolve(reports)
|
|
15
|
+
# normalize(reports)
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# private
|
|
19
|
+
# def normalize(raw)
|
|
20
|
+
# raw.downcase
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # good
|
|
26
|
+
# module Architecture
|
|
27
|
+
# extend self
|
|
28
|
+
#
|
|
29
|
+
# def resolve(reports)
|
|
30
|
+
# normalize(reports)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# private
|
|
34
|
+
# def normalize(raw)
|
|
35
|
+
# raw.downcase
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
class RuboCop::Cop::Callbacksystems::PreferModuleForStaticClass < RuboCop::Cop::Callbacksystems::Base
|
|
40
|
+
include RuboCop::Callbacksystems::ProjectIndex::Support
|
|
41
|
+
|
|
42
|
+
def on_class(node)
|
|
43
|
+
if project_index_reliable?
|
|
44
|
+
inspect_candidate StaticClass.new(node), node:
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def inspect_candidate(analysis, node:)
|
|
50
|
+
declaration = convertible_declaration_of(node) if analysis.candidate?
|
|
51
|
+
report analysis if declaration && module_preferred?(analysis, declaration:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def convertible_declaration_of(node)
|
|
55
|
+
declaration = resolve_constant_in_index(node.identifier)
|
|
56
|
+
declaration if declaration.is_a?(Rubydex::Class) && project_declaration_reliable?(declaration)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def module_preferred?(analysis, declaration:)
|
|
60
|
+
analysis.compatible_with_module?(declaration) && !class_required?(declaration)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def class_required?(declaration)
|
|
64
|
+
inherited_callback_observable? || definitions_in_other_files(declaration.definitions).any? ||
|
|
65
|
+
!declaration.definitions.one? ||
|
|
66
|
+
declaration.descendants.any? { it.name != declaration.name } ||
|
|
67
|
+
!project_references.compatible_with_module?(declaration)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def inherited_callback_observable?
|
|
71
|
+
[ project_index["Object"].singleton_class, project_index["Class"] ].compact.any? do |namespace|
|
|
72
|
+
namespace.find_member("inherited()")&.definitions&.any?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def project_references
|
|
77
|
+
ProjectReferences.for(project_index)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Every reference must prove that replacing the declaration with a module preserves what that line does.
|
|
81
|
+
class ProjectReferences
|
|
82
|
+
extend RuboCop::Callbacksystems::ProjectIndex::Cache
|
|
83
|
+
|
|
84
|
+
def initialize(project_index)
|
|
85
|
+
@method_references_by_receiver = grouped_references(project_index.method_references) { receiver_name_of(it) }
|
|
86
|
+
@constant_references_by_owner = grouped_references(project_index.constant_references) { owner_name_of(it) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def compatible_with_module?(declaration)
|
|
90
|
+
declaration.references.all? do |reference|
|
|
91
|
+
ReferenceUse.new(
|
|
92
|
+
reference,
|
|
93
|
+
declaration:,
|
|
94
|
+
method_references: method_references_for(declaration),
|
|
95
|
+
constant_references: constant_references_for(declaration)
|
|
96
|
+
).compatible?
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
attr_reader :method_references_by_receiver, :constant_references_by_owner
|
|
102
|
+
|
|
103
|
+
def grouped_references(references)
|
|
104
|
+
references.each_with_object({}) do |reference, groups|
|
|
105
|
+
yield(reference)&.then do |group|
|
|
106
|
+
locations = groups[group] ||= {}
|
|
107
|
+
(locations[location_key(reference.location)] ||= []) << reference
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def location_key(location)
|
|
113
|
+
[ location.uri, location.start_line, location.start_column ]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def receiver_name_of(reference)
|
|
117
|
+
receiver = reference.receiver
|
|
118
|
+
receiver.attached_class.name if receiver.is_a?(Rubydex::SingletonClass)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def owner_name_of(reference)
|
|
122
|
+
reference.declaration.owner.name if reference.is_a?(Rubydex::ResolvedConstantReference)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def method_references_for(declaration)
|
|
126
|
+
method_references_by_receiver.fetch(declaration.name) { {} }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def constant_references_for(declaration)
|
|
130
|
+
constant_references_by_owner.fetch(declaration.name) { {} }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class ReferenceUse
|
|
134
|
+
def initialize(reference, declaration:, method_references:, constant_references:)
|
|
135
|
+
@reference = reference
|
|
136
|
+
@declaration = declaration
|
|
137
|
+
@method_references = method_references
|
|
138
|
+
@constant_references = constant_references
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def compatible?
|
|
142
|
+
direct_declared_method? || namespace_reference?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
attr_reader :reference, :declaration, :method_references, :constant_references
|
|
147
|
+
|
|
148
|
+
def direct_declared_method?
|
|
149
|
+
adjacent_references_in(method_references).any? { declared_on_singleton?(it.name) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def adjacent_references_in(references, separator_width: nil)
|
|
153
|
+
expected_columns(separator_width).flat_map do |column|
|
|
154
|
+
references.fetch(location_key(column)) { [] }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def expected_columns(separator_width)
|
|
159
|
+
widths = separator_width ? [ separator_width ] : [ 1, 2 ]
|
|
160
|
+
widths.to_set { reference.location.end_column + it }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def location_key(column)
|
|
164
|
+
[ reference.location.uri, reference.location.end_line, column ]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def declared_on_singleton?(method_name)
|
|
168
|
+
declaration.singleton_class.member("#{method_name}()").present?
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def namespace_reference?
|
|
172
|
+
adjacent_references_in(constant_references, separator_width: 2).any?
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
class StaticClass
|
|
178
|
+
include RuboCop::Callbacksystems::Helpers
|
|
179
|
+
|
|
180
|
+
MESSAGE = "Prefer a module with `extend self` to a class holding only class methods."
|
|
181
|
+
CLASS_ONLY_CALLBACKS = %i[ inherited ]
|
|
182
|
+
VISIBILITY_METHODS = %i[ private protected public ]
|
|
183
|
+
|
|
184
|
+
def initialize(node)
|
|
185
|
+
@node = node
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def offense
|
|
189
|
+
RuboCop::Callbacksystems::Offense.new(node, MESSAGE)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def candidate?
|
|
193
|
+
convertible? && private_singleton_section?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def compatible_with_module?(declaration)
|
|
197
|
+
!class_semantics_used? && singleton_calls_owned_by?(declaration)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
attr_reader :node
|
|
202
|
+
|
|
203
|
+
def convertible?
|
|
204
|
+
node.parent_class.nil? && only_class_level_statements?
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def only_class_level_statements?
|
|
208
|
+
statements.any? && statements.all? { class_level_statement?(it) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def statements
|
|
212
|
+
@statements ||= statements_in(node.body)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def class_level_statement?(statement)
|
|
216
|
+
statement.type?(:casgn, :defs) || extend_call?(statement) || singleton_section?(statement)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def extend_call?(statement)
|
|
220
|
+
bare_send?(statement) && statement.method?(:extend)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def private_singleton_section?
|
|
224
|
+
statements.select { singleton_section?(it) }.any? { holds_private_section?(it) }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def holds_private_section?(section)
|
|
228
|
+
private_modifier_in(section.body).present?
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def class_semantics_used?
|
|
232
|
+
class_only_callback_defined? || method_name_rebound? || singleton_super? || self_value_used?
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def class_only_callback_defined?
|
|
236
|
+
nodes_in(node.body, :def, :defs).any? { CLASS_ONLY_CALLBACKS.include?(it.method_name) }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def method_name_rebound?
|
|
240
|
+
nodes_in(node.body, :alias, :undef).any? || nodes_in(node.body, :send, :csend).any? do |send_node|
|
|
241
|
+
send_node.method?(:alias_method) || send_node.method?(:undef_method)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def singleton_super?
|
|
246
|
+
nodes_in(node.body, :super, :zsuper).any?
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def self_value_used?
|
|
250
|
+
nodes_in(node.body, :self).any? do |self_node|
|
|
251
|
+
enclosing_class_or_module_of(self_node).equal?(node) && !structural_self?(self_node)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def singleton_calls_owned_by?(declaration)
|
|
256
|
+
candidate_singleton_calls.all? do |send_node|
|
|
257
|
+
visibility_modifier?(send_node) || owned_method?(send_node.method_name, declaration:)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def candidate_singleton_calls
|
|
262
|
+
nodes_in(node.body, :send, :csend).select do |send_node|
|
|
263
|
+
call_on_self?(send_node) && candidate_singleton_domain?(send_node)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def candidate_singleton_domain?(send_node)
|
|
268
|
+
RuboCop::Callbacksystems::Methods::Domain.new(send_node).then do |domain|
|
|
269
|
+
(domain.container.equal?(node) && domain.identity.present? && domain.identity.all? { it == :self }) ||
|
|
270
|
+
opaque_block_in_candidate?(domain.container)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def opaque_block_in_candidate?(container)
|
|
275
|
+
any_block_type?(container) && enclosing_class_or_module_of(container).equal?(node)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def visibility_modifier?(send_node)
|
|
279
|
+
enclosing_method_of(send_node).nil? && VISIBILITY_METHODS.include?(send_node.method_name)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def owned_method?(method_name, declaration:)
|
|
283
|
+
declaration.singleton_class.member("#{method_name}()").present?
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Detects array access with numeric indices that could use ordinal methods.
|
|
2
|
+
# `items.second` says which element it wants, where `items[1]` makes the
|
|
3
|
+
# reader count from zero.
|
|
4
|
+
#
|
|
5
|
+
# `[]` answers for a Hash, a MatchData and a String too, and the ordinal methods
|
|
6
|
+
# come from Array alone, so a receiver we can read as one of those is left alone.
|
|
7
|
+
# That includes a receiver read out of another `[]`, whose type the file never
|
|
8
|
+
# shows, and a local variable whose assignment in the same scope hands it one of
|
|
9
|
+
# those.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad
|
|
13
|
+
# items[1]
|
|
14
|
+
# items[2]
|
|
15
|
+
# items[3]
|
|
16
|
+
# items[4]
|
|
17
|
+
# items[-2]
|
|
18
|
+
# items[-3]
|
|
19
|
+
#
|
|
20
|
+
# # good
|
|
21
|
+
# items.second
|
|
22
|
+
# items.third
|
|
23
|
+
# items.fourth
|
|
24
|
+
# items.fifth
|
|
25
|
+
# items.second_to_last
|
|
26
|
+
# items.third_to_last
|
|
27
|
+
#
|
|
28
|
+
class RuboCop::Cop::Callbacksystems::PreferOrdinalArrayAccess < RuboCop::Cop::Callbacksystems::Base
|
|
29
|
+
extend RuboCop::Cop::AutoCorrector
|
|
30
|
+
|
|
31
|
+
def on_new_investigation
|
|
32
|
+
@local_variables = RuboCop::Callbacksystems::Execution::LocalVariableOccurrences.new(processed_source.ast)
|
|
33
|
+
@source_comments = RuboCop::Callbacksystems::Source::Comments.for(processed_source)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def on_send(node)
|
|
37
|
+
report IndexedRead.new(node, @local_variables, @source_comments)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
alias on_csend on_send
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
ORDINAL_METHODS = {
|
|
44
|
+
1 => :second,
|
|
45
|
+
2 => :third,
|
|
46
|
+
3 => :fourth,
|
|
47
|
+
4 => :fifth,
|
|
48
|
+
-2 => :second_to_last,
|
|
49
|
+
-3 => :third_to_last
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
NON_ARRAY_LITERAL_TYPES = %i[ dsym hash int regexp sym ]
|
|
53
|
+
# `[]` reads out of anything, `match` and its friends answer a MatchData, and the rest answer a Hash.
|
|
54
|
+
NON_ARRAY_RESULT_METHODS = %i[
|
|
55
|
+
[] last_match match match? named_captures to_h to_hash group_by index_by index_with tally params
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# One `items[1]`, offending when an ordinal method names that position and nothing says the receiver is no array.
|
|
59
|
+
class IndexedRead
|
|
60
|
+
include RuboCop::Callbacksystems::Helpers
|
|
61
|
+
extend RuboCop::AST::NodePattern::Macros
|
|
62
|
+
|
|
63
|
+
MESSAGE = "Use `%<method>s` instead of `[%<index>s]`."
|
|
64
|
+
|
|
65
|
+
# @!method bracket_access_with_int?(node)
|
|
66
|
+
def_node_matcher :bracket_access_with_int?, <<~PATTERN
|
|
67
|
+
(call $_ :[] (int $_index))
|
|
68
|
+
PATTERN
|
|
69
|
+
|
|
70
|
+
def initialize(node, local_variables, source_comments)
|
|
71
|
+
@node = node
|
|
72
|
+
@local_variables = local_variables
|
|
73
|
+
@source_comments = source_comments
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def offense
|
|
77
|
+
if ordinal_method && array_receiver?
|
|
78
|
+
RuboCop::Callbacksystems::Offense.new(node, message, correcting: correction_keeps_comments?) { correct(it) }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
attr_reader :node, :local_variables, :source_comments
|
|
84
|
+
|
|
85
|
+
def ordinal_method
|
|
86
|
+
ORDINAL_METHODS[index] if captures
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def captures
|
|
90
|
+
@captures ||= bracket_access_with_int?(node)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def index
|
|
94
|
+
captures.second
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def array_receiver?
|
|
98
|
+
!non_array_value?(receiver) && !assigned_a_non_array?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def non_array_value?(value)
|
|
102
|
+
reads_as_string?(value) ||
|
|
103
|
+
reads_as?(value, literals: NON_ARRAY_LITERAL_TYPES, methods: NON_ARRAY_RESULT_METHODS)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def receiver
|
|
107
|
+
captures.first
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def assigned_a_non_array?
|
|
111
|
+
receiver.lvar_type? && assignments.any? do |assignment|
|
|
112
|
+
assignment.match_var_type? || assignment.expression.nil? || non_array_value?(assignment.expression)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def assignments
|
|
117
|
+
local_variables.named(receiver.name, around: receiver).select { it.type?(:lvasgn, :match_var) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def message
|
|
121
|
+
format(MESSAGE, method: ordinal_method, index: index)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def correction_keeps_comments?
|
|
125
|
+
!source_comments.any_within?(node)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def correct(corrector)
|
|
129
|
+
corrector.replace(node, "#{receiver.source}#{call_operator}#{ordinal_method}")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def call_operator
|
|
133
|
+
node.csend_type? ? "&." : "."
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# A negative `return unless` guard followed by a happy path reads more naturally
|
|
2
|
+
# as `if condition; ...; end`. The wrap adds one `if` block, so it is only applied when
|
|
3
|
+
# the happy path's own nesting plus that extra level still fits inside the
|
|
4
|
+
# configured `Metrics/BlockNesting` limit; anything past that would push the
|
|
5
|
+
# method past the nesting limit. Method-length rules already cap how long a
|
|
6
|
+
# happy path can be, so the only real cost of the wrap is the indent level it adds.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # bad - leading negative guard
|
|
10
|
+
# def label
|
|
11
|
+
# return unless ready?
|
|
12
|
+
# compute_label
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# # good - positive wrap
|
|
16
|
+
# def label
|
|
17
|
+
# if ready?
|
|
18
|
+
# compute_label
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
class RuboCop::Cop::Callbacksystems::PreferPositiveWrap < RuboCop::Cop::Callbacksystems::Base
|
|
23
|
+
extend RuboCop::Cop::AutoCorrector
|
|
24
|
+
|
|
25
|
+
def on_def(node)
|
|
26
|
+
report Guard.new(node.body, processed_source, nesting: block_nesting)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias on_defs on_def
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def block_nesting
|
|
33
|
+
BlockNesting.new(config.for_cop("Metrics/BlockNesting"))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The configured core metric, reduced to the one question this rewrite needs to ask.
|
|
37
|
+
class BlockNesting
|
|
38
|
+
NESTING_TYPES = %i[ case case_match if while while_post until until_post for resbody ].to_set
|
|
39
|
+
|
|
40
|
+
def initialize(config)
|
|
41
|
+
@max = config["Max"]
|
|
42
|
+
@count_blocks = config.fetch("CountBlocks", false)
|
|
43
|
+
@count_modifier_forms = config.fetch("CountModifierForms", false)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def allows_wrap?(nodes)
|
|
47
|
+
nodes.map { depth_of(it) }.max.to_i + 1 <= max
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
attr_reader :max, :count_blocks, :count_modifier_forms
|
|
52
|
+
|
|
53
|
+
def depth_of(node)
|
|
54
|
+
{}.compare_by_identity.then do |depths|
|
|
55
|
+
Descendants.new(node).to_a.reverse_each do |descendant|
|
|
56
|
+
depths[descendant] = (counts?(descendant) ? 1 : 0) +
|
|
57
|
+
descendant.each_child_node.map { depths.fetch(it) }.max.to_i
|
|
58
|
+
end
|
|
59
|
+
depths.fetch(node)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def counts?(node)
|
|
64
|
+
NESTING_TYPES.include?(node.type) ? counts_control_flow?(node) : count_blocks && node.any_block_type?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def counts_control_flow?(node)
|
|
68
|
+
!node.if_type? || counts_if?(node)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def counts_if?(node)
|
|
72
|
+
!node.elsif? && (!node.modifier_form? || count_modifier_forms)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class Descendants
|
|
76
|
+
include Enumerable
|
|
77
|
+
|
|
78
|
+
def initialize(root)
|
|
79
|
+
@root = root
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def each
|
|
83
|
+
if block_given?
|
|
84
|
+
pending = [ root ]
|
|
85
|
+
until pending.empty?
|
|
86
|
+
node = pending.pop
|
|
87
|
+
yield node
|
|
88
|
+
pending.concat(node.each_child_node.to_a.reverse)
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
to_enum(__method__)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
attr_reader :root
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The leading negative guard inside a method body, plus the happy path that follows it.
|
|
101
|
+
class Guard
|
|
102
|
+
include RuboCop::Callbacksystems::Helpers
|
|
103
|
+
|
|
104
|
+
MESSAGE = "Wrap positively: `if condition; ...; end` instead of a leading negative guard."
|
|
105
|
+
|
|
106
|
+
def initialize(body, processed_source, nesting:)
|
|
107
|
+
@body = body
|
|
108
|
+
@processed_source = processed_source
|
|
109
|
+
@source_comments = RuboCop::Callbacksystems::Source::Comments.for(processed_source)
|
|
110
|
+
@nesting = nesting
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def offense
|
|
114
|
+
RuboCop::Callbacksystems::Offense.new(guard, MESSAGE, correcting: correctable?) { correct(it) } if wrappable?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
attr_reader :body, :processed_source, :source_comments, :nesting
|
|
119
|
+
|
|
120
|
+
def wrappable?
|
|
121
|
+
statements.size >= 2 && negative_return_guard?(guard) && nesting.allows_wrap?(happy)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def statements
|
|
125
|
+
statements_in(body)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def negative_return_guard?(node)
|
|
129
|
+
node.if_type? && node.unless? && node.if_branch&.return_type?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def guard
|
|
133
|
+
statements.first
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def happy
|
|
137
|
+
statements.drop(1)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# An existing `else` is observable on the positive path and cannot be folded into this rewrite.
|
|
141
|
+
def correctable?
|
|
142
|
+
guard_shape_correctable? && rewrite_preserves_source?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def guard_shape_correctable?
|
|
146
|
+
guard.else_branch.nil? && guard_value_stands_alone?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def guard_value_stands_alone?
|
|
150
|
+
guard_values.size != 1 || !unpackaged_splat?(guard_values.first)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def guard_values
|
|
154
|
+
guard.if_branch.children
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def unpackaged_splat?(value)
|
|
158
|
+
value.splat_type? || (value.hash_type? && !value.braces?)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def rewrite_preserves_source?
|
|
162
|
+
comments_preserved? && heredocs_preserved?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def comments_preserved?
|
|
166
|
+
comments_in_replacement.all? { preserved_comment?(it) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def comments_in_replacement
|
|
170
|
+
source_comments.within(replacement_range)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def replacement_range
|
|
174
|
+
guard.source_range.with(end_pos: happy_range.end_pos)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def happy_range
|
|
178
|
+
@happy_range ||= first_happy_block.range.with(end_pos: last_happy_block.range.end_pos)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def first_happy_block
|
|
182
|
+
@first_happy_block ||= statement_block_for(happy.first)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def statement_block_for(node)
|
|
186
|
+
RuboCop::Callbacksystems::Source::StatementWithComments.new(node, processed_source)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def last_happy_block
|
|
190
|
+
@last_happy_block ||= statement_block_for(happy.last)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def preserved_comment?(comment)
|
|
194
|
+
comment.equal?(guard_trailing_comment) || preserved_comment_ranges.any? { it.contains?(comment.source_range) }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def guard_trailing_comment
|
|
198
|
+
@guard_trailing_comment ||= source_comments.trailing_comment_on(guard.last_line)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def preserved_comment_ranges
|
|
202
|
+
[ happy_range, guard.condition.source_range, *guard_values.map(&:source_range) ]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def heredocs_preserved?
|
|
206
|
+
!carries_heredoc?(guard) && happy_heredocs.all? { it.source.start_with?("<<~") }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def happy_heredocs
|
|
210
|
+
happy.flat_map { it.each_node(:str, :dstr, :xstr).select { it.loc?(:heredoc_body) } }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def correct(corrector)
|
|
214
|
+
corrector.replace(replacement_range, wrapped_source)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def wrapped_source
|
|
218
|
+
"#{guard_header}\n#{indented_happy}\n#{else_branch}#{indent}end"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def guard_header
|
|
222
|
+
"if #{guard.condition.source}#{guard_comment_source}"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def guard_comment_source
|
|
226
|
+
guard_trailing_comment&.then { " #{it.text}" }.to_s
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def indented_happy
|
|
230
|
+
[ "#{first_line_indent}#{happy_lines.first}", *happy_lines.drop(1).map { indented_line(it) } ].join
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def first_line_indent
|
|
234
|
+
happy_range.column.zero? ? " " : "#{indent} "
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def indent
|
|
238
|
+
indentation_of(guard)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def happy_lines
|
|
242
|
+
@happy_lines ||= happy_source.lines
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def happy_source
|
|
246
|
+
happy_range.source
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def indented_line(line)
|
|
250
|
+
line.strip.empty? ? line : " #{line}"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def else_branch
|
|
254
|
+
guard_value_source ? "#{indent}else\n#{indent} #{guard_value_source}\n" : ""
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def guard_value_source
|
|
258
|
+
case guard_values
|
|
259
|
+
in [] then nil
|
|
260
|
+
in [ value ] then value.source
|
|
261
|
+
else "[ #{guard_values.map(&:source).join(", ")} ]"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|