rubocop 1.91.0 → 1.92.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 +4 -4
- data/README.md +1 -1
- data/config/default.yml +3 -1
- data/lib/rubocop/cached_data.rb +47 -7
- data/lib/rubocop/changed_files.rb +35 -13
- data/lib/rubocop/cli/command/cop_names.rb +57 -0
- data/lib/rubocop/cli/command/execute_runner.rb +2 -2
- data/lib/rubocop/cli/command/explain.rb +34 -0
- data/lib/rubocop/cli/command/show_cops.rb +6 -0
- data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
- data/lib/rubocop/cli.rb +3 -2
- data/lib/rubocop/comment_config.rb +17 -8
- data/lib/rubocop/config_loader.rb +6 -3
- data/lib/rubocop/config_loader_resolver.rb +37 -12
- data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
- data/lib/rubocop/config_validator.rb +11 -0
- data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
- data/lib/rubocop/cop/base.rb +42 -21
- data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
- data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
- data/lib/rubocop/cop/cop_documentation.rb +81 -0
- data/lib/rubocop/cop/cop_explanation.rb +140 -0
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +22 -7
- data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
- data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
- data/lib/rubocop/cop/documentation.rb +1 -1
- data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
- data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
- data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
- data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
- data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
- data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
- data/lib/rubocop/cop/layout/class_structure.rb +9 -1
- data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
- data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
- data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
- data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
- data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
- data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
- data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
- data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
- data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
- data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -0
- data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
- data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
- data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
- data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
- data/lib/rubocop/cop/layout/line_length.rb +52 -18
- data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
- data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
- data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +7 -0
- data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
- data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
- data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
- data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
- data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
- data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
- data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
- data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
- data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
- data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
- data/lib/rubocop/cop/lint/loop.rb +24 -2
- data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
- data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
- data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
- data/lib/rubocop/cop/lint/void.rb +8 -1
- data/lib/rubocop/cop/metrics/class_length.rb +4 -2
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
- data/lib/rubocop/cop/metrics/module_length.rb +4 -2
- data/lib/rubocop/cop/mixin/alignment.rb +4 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +17 -14
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
- data/lib/rubocop/cop/mixin/code_length.rb +10 -2
- data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
- data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
- data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +15 -1
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
- data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
- data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
- data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
- data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
- data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
- data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
- data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
- data/lib/rubocop/cop/mixin/statement_modifier.rb +22 -2
- data/lib/rubocop/cop/mixin.rb +2 -0
- data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
- data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
- data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
- data/lib/rubocop/cop/offense.rb +59 -3
- data/lib/rubocop/cop/registry.rb +1 -1
- data/lib/rubocop/cop/security/json_load.rb +16 -4
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +7 -0
- data/lib/rubocop/cop/style/alias.rb +18 -5
- data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
- data/lib/rubocop/cop/style/and_or.rb +4 -0
- data/lib/rubocop/cop/style/arguments_forwarding.rb +4 -1
- data/lib/rubocop/cop/style/attr.rb +3 -4
- data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
- data/lib/rubocop/cop/style/block_delimiters.rb +29 -3
- data/lib/rubocop/cop/style/case_like_if.rb +6 -3
- data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
- data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
- data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
- data/lib/rubocop/cop/style/concat_array_literals.rb +46 -7
- data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
- data/lib/rubocop/cop/style/double_negation.rb +11 -0
- data/lib/rubocop/cop/style/empty_literal.rb +1 -1
- data/lib/rubocop/cop/style/empty_method.rb +3 -1
- data/lib/rubocop/cop/style/endless_method.rb +67 -6
- data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
- data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
- data/lib/rubocop/cop/style/float_division.rb +22 -2
- data/lib/rubocop/cop/style/for.rb +40 -2
- data/lib/rubocop/cop/style/format_string.rb +28 -6
- data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
- data/lib/rubocop/cop/style/guard_clause.rb +9 -1
- data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
- data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
- data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
- data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
- data/lib/rubocop/cop/style/if_unless_modifier.rb +19 -20
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
- data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
- data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
- data/lib/rubocop/cop/style/it_block_parameter.rb +32 -3
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
- data/lib/rubocop/cop/style/lambda.rb +18 -0
- data/lib/rubocop/cop/style/lambda_call.rb +7 -2
- data/lib/rubocop/cop/style/map_into_array.rb +1 -2
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +45 -10
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
- data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
- data/lib/rubocop/cop/style/missing_else.rb +7 -1
- data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
- data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
- data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
- data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
- data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
- data/lib/rubocop/cop/style/mutable_constant.rb +28 -1
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
- data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
- data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
- data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
- data/lib/rubocop/cop/style/next.rb +24 -10
- data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
- data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
- data/lib/rubocop/cop/style/not.rb +10 -5
- data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
- data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
- data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
- data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
- data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
- data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
- data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
- data/lib/rubocop/cop/style/redundant_freeze.rb +10 -1
- data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
- data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
- data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
- data/lib/rubocop/cop/style/require_order.rb +9 -0
- data/lib/rubocop/cop/style/rescue_modifier.rb +5 -9
- data/lib/rubocop/cop/style/return_nil.rb +17 -2
- data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
- data/lib/rubocop/cop/style/sample.rb +9 -8
- data/lib/rubocop/cop/style/self_assignment.rb +12 -15
- data/lib/rubocop/cop/style/semicolon.rb +16 -5
- data/lib/rubocop/cop/style/single_line_methods.rb +16 -4
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
- data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
- data/lib/rubocop/cop/style/super_arguments.rb +3 -0
- data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
- data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
- data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
- data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
- data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
- data/lib/rubocop/cop/team.rb +10 -5
- data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
- data/lib/rubocop/cop/variable_force/variable.rb +14 -1
- data/lib/rubocop/formatter/base_formatter.rb +22 -0
- data/lib/rubocop/formatter/formatter_set.rb +4 -0
- data/lib/rubocop/formatter/json_formatter.rb +71 -1
- data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
- data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
- data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
- data/lib/rubocop/lsp/runtime.rb +47 -11
- data/lib/rubocop/lsp/stdin_runner.rb +11 -5
- data/lib/rubocop/lsp.rb +5 -2
- data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
- data/lib/rubocop/mcp/explain_tool.rb +68 -0
- data/lib/rubocop/mcp/scope.rb +65 -0
- data/lib/rubocop/mcp/server.rb +184 -76
- data/lib/rubocop/mcp/source_files.rb +35 -0
- data/lib/rubocop/offense_limit.rb +30 -0
- data/lib/rubocop/options.rb +45 -10
- data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
- data/lib/rubocop/remote_config.rb +4 -1
- data/lib/rubocop/result_cache.rb +20 -13
- data/lib/rubocop/runner.rb +109 -41
- data/lib/rubocop/server/cache.rb +2 -2
- data/lib/rubocop/util.rb +69 -0
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +5 -0
- metadata +16 -5
|
@@ -60,19 +60,18 @@ module RuboCop
|
|
|
60
60
|
# end
|
|
61
61
|
#
|
|
62
62
|
class CombinableLoops < Base
|
|
63
|
+
include RangeHelp
|
|
63
64
|
extend AutoCorrector
|
|
64
65
|
|
|
65
66
|
MSG = 'Combine this loop with the previous loop.'
|
|
66
67
|
|
|
67
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
68
68
|
def on_block(node)
|
|
69
69
|
return unless node.parent&.begin_type?
|
|
70
70
|
return unless collection_looping_method?(node)
|
|
71
|
-
return unless
|
|
72
|
-
return unless node.body && node.left_sibling.body
|
|
71
|
+
return unless combinable_looping_blocks?(node, node.left_sibling)
|
|
73
72
|
|
|
74
73
|
add_offense(node) do |corrector|
|
|
75
|
-
next unless node
|
|
74
|
+
next unless combinable_blocks?(node, node.left_sibling)
|
|
76
75
|
|
|
77
76
|
combine_with_left_sibling(corrector, node)
|
|
78
77
|
end
|
|
@@ -87,9 +86,7 @@ module RuboCop
|
|
|
87
86
|
return unless node.body && node.left_sibling.body
|
|
88
87
|
|
|
89
88
|
add_offense(node) do |corrector|
|
|
90
|
-
|
|
91
|
-
# body referencing an undefined variable, so only autocorrect when they match.
|
|
92
|
-
next unless node.variable == node.left_sibling.variable
|
|
89
|
+
next unless combinable_fors?(node, node.left_sibling)
|
|
93
90
|
|
|
94
91
|
combine_with_left_sibling(corrector, node)
|
|
95
92
|
end
|
|
@@ -110,25 +107,102 @@ module RuboCop
|
|
|
110
107
|
sibling.send_node.arguments == node.send_node.arguments
|
|
111
108
|
end
|
|
112
109
|
|
|
110
|
+
def combinable_looping_blocks?(node, sibling)
|
|
111
|
+
same_collection_looping_block?(node, sibling) && node.body && sibling.body
|
|
112
|
+
end
|
|
113
|
+
|
|
113
114
|
def same_collection_looping_for?(node, sibling)
|
|
114
115
|
sibling&.for_type? && node.collection == sibling.collection
|
|
115
116
|
end
|
|
116
117
|
|
|
118
|
+
def combinable_blocks?(node, sibling)
|
|
119
|
+
return false unless node.arguments == sibling.arguments
|
|
120
|
+
# Numbered and `it` parameters are implicit, so `arguments` is empty for both.
|
|
121
|
+
return false unless node.argument_list == sibling.argument_list
|
|
122
|
+
return false if directive_after_opening?(node)
|
|
123
|
+
|
|
124
|
+
!node.body.type?(:rescue, :ensure) && !sibling.body.type?(:rescue, :ensure)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Combining loops with different iteration variables would leave the second
|
|
128
|
+
# body referencing an undefined variable, so only autocorrect when they match.
|
|
129
|
+
def combinable_fors?(node, sibling)
|
|
130
|
+
node.variable == sibling.variable && !directive_after_opening?(node)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The opening is removed, so a directive after it would end up on a line of
|
|
134
|
+
# its own, where it covers the rest of the file instead of a single line.
|
|
135
|
+
def directive_after_opening?(node)
|
|
136
|
+
opening_line = loop_opening(node).line
|
|
137
|
+
return false if opening_line == node.body.first_line
|
|
138
|
+
|
|
139
|
+
comment = processed_source.comment_at_line(opening_line)
|
|
140
|
+
comment && DirectiveComment.new(comment).start_with_marker?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The block arguments or `{`/`do` of a block, or the `do` or collection of a `for` loop.
|
|
144
|
+
def loop_opening(node)
|
|
145
|
+
if node.for_type?
|
|
146
|
+
node.loc.begin || node.collection.source_range
|
|
147
|
+
else
|
|
148
|
+
(node.block_type? && node.arguments.source_range) || node.loc.begin
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
117
152
|
def combine_with_left_sibling(corrector, node)
|
|
118
|
-
corrector.remove(node.left_sibling
|
|
119
|
-
corrector.remove(
|
|
153
|
+
corrector.remove(closing_with_preceding_space(node.left_sibling, node))
|
|
154
|
+
corrector.remove(opening_with_following_space(node))
|
|
120
155
|
|
|
121
156
|
correct_end_of_block(corrector, node)
|
|
122
157
|
end
|
|
123
158
|
|
|
159
|
+
# Comments and heredoc bodies before the closing delimiter are kept. When
|
|
160
|
+
# the next loop starts on the delimiter's line, the line break before the
|
|
161
|
+
# delimiter is kept too, so that a comment doesn't swallow the next loop.
|
|
162
|
+
def closing_with_preceding_space(loop, next_loop)
|
|
163
|
+
separate_lines = !same_line?(loop.loc.end, next_loop)
|
|
164
|
+
|
|
165
|
+
range_with_surrounding_space(
|
|
166
|
+
loop.loc.end, side: :left, newlines: separate_lines, whitespace: separate_lines
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Comments between the opening and the body are kept.
|
|
171
|
+
def opening_with_following_space(node)
|
|
172
|
+
opening = node.source_range.begin.join(loop_opening(node))
|
|
173
|
+
|
|
174
|
+
range_with_surrounding_space(opening, side: :right, whitespace: true)
|
|
175
|
+
end
|
|
176
|
+
|
|
124
177
|
def correct_end_of_block(corrector, node)
|
|
125
178
|
return unless node.left_sibling.respond_to?(:braces?)
|
|
126
|
-
return if node
|
|
179
|
+
return if combined_with_right_sibling?(node)
|
|
127
180
|
|
|
128
|
-
end_of_block = node.
|
|
181
|
+
end_of_block = first_combined_block(node).braces? ? '}' : ' end'
|
|
129
182
|
corrector.remove(node.loc.end)
|
|
130
183
|
corrector.insert_before(node.source_range.end, end_of_block)
|
|
131
184
|
end
|
|
185
|
+
|
|
186
|
+
def combined_with_right_sibling?(node)
|
|
187
|
+
sibling = node.right_sibling
|
|
188
|
+
return false unless sibling&.any_block_type?
|
|
189
|
+
# A disabled loop isn't corrected, so it doesn't get merged into this one.
|
|
190
|
+
return false unless enabled_lines?(sibling.source_range)
|
|
191
|
+
|
|
192
|
+
same_collection_looping_block?(sibling, node) && sibling.body &&
|
|
193
|
+
combinable_blocks?(sibling, node)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def first_combined_block(node)
|
|
197
|
+
first = node.left_sibling
|
|
198
|
+
|
|
199
|
+
while (previous = first.left_sibling) && enabled_lines?(first.source_range) &&
|
|
200
|
+
combinable_looping_blocks?(first, previous) && combinable_blocks?(first, previous)
|
|
201
|
+
first = previous
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
first
|
|
205
|
+
end
|
|
132
206
|
end
|
|
133
207
|
end
|
|
134
208
|
end
|
|
@@ -23,6 +23,7 @@ module RuboCop
|
|
|
23
23
|
# list.push(qux, quux, corge)
|
|
24
24
|
#
|
|
25
25
|
class ConcatArrayLiterals < Base
|
|
26
|
+
include RangeHelp
|
|
26
27
|
extend AutoCorrector
|
|
27
28
|
|
|
28
29
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
|
@@ -30,6 +31,14 @@ module RuboCop
|
|
|
30
31
|
'Use `push` with elements as arguments without array brackets instead of `%<current>s`.'
|
|
31
32
|
RESTRICT_ON_SEND = %i[concat].freeze
|
|
32
33
|
|
|
34
|
+
# `Style/MethodCallWithArgsParentheses` removing the parentheses of the `push(...)`
|
|
35
|
+
# this cop writes in the same pass drops the line continuation a multiline call needs.
|
|
36
|
+
# `Style/TrailingCommaInArguments` adds a comma after the array literal this cop
|
|
37
|
+
# replaces with its elements in the same pass.
|
|
38
|
+
def self.autocorrect_incompatible_with
|
|
39
|
+
[Style::MethodCallWithArgsParentheses, Style::TrailingCommaInArguments]
|
|
40
|
+
end
|
|
41
|
+
|
|
33
42
|
# rubocop:disable-next Metrics
|
|
34
43
|
def on_send(node)
|
|
35
44
|
return if node.arguments.empty?
|
|
@@ -54,17 +63,13 @@ module RuboCop
|
|
|
54
63
|
if use_percent_literal
|
|
55
64
|
next unless prefer
|
|
56
65
|
|
|
57
|
-
corrector
|
|
66
|
+
rebuild_call(corrector, offense, prefer)
|
|
58
67
|
elsif node.arguments.any? { |argument| argument.children.empty? }
|
|
59
68
|
# In-place bracket removal would leave dangling commas (e.g.
|
|
60
69
|
# `concat([], [b])` -> `push(, b)`), so rebuild the call instead.
|
|
61
|
-
corrector
|
|
70
|
+
rebuild_call(corrector, offense, preferred_method(node))
|
|
62
71
|
else
|
|
63
|
-
corrector
|
|
64
|
-
node.arguments.each do |argument|
|
|
65
|
-
corrector.remove(argument.loc.begin)
|
|
66
|
-
corrector.remove(argument.loc.end)
|
|
67
|
-
end
|
|
72
|
+
remove_brackets(corrector, node)
|
|
68
73
|
end
|
|
69
74
|
end
|
|
70
75
|
end
|
|
@@ -94,6 +99,40 @@ module RuboCop
|
|
|
94
99
|
arg.children.all? { |child| child.type?(:str, :sym) }
|
|
95
100
|
end
|
|
96
101
|
end
|
|
102
|
+
|
|
103
|
+
def rebuild_call(corrector, offense, replacement)
|
|
104
|
+
# The comments inside the call would be lost.
|
|
105
|
+
return if comment_in?(offense)
|
|
106
|
+
|
|
107
|
+
corrector.replace(offense, replacement)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def comment_in?(range)
|
|
111
|
+
processed_source.each_comment_in_lines(range.line..range.last_line).any? do |comment|
|
|
112
|
+
range.contains?(comment.source_range)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def remove_brackets(corrector, node)
|
|
117
|
+
closing_ranges = node.arguments.map { |argument| closing_bracket_range(node, argument) }
|
|
118
|
+
# A comment or a heredoc body there can't be moved out of the way.
|
|
119
|
+
return unless closing_ranges.all? { |range| range.source.match?(/\A[\s,]*\]\z/) }
|
|
120
|
+
|
|
121
|
+
corrector.replace(node.loc.selector, 'push')
|
|
122
|
+
node.arguments.zip(closing_ranges) do |argument, closing_range|
|
|
123
|
+
corrector.remove(argument.loc.begin)
|
|
124
|
+
corrector.remove(closing_range)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Whatever sits between the last element and the `]` of an array that isn't the
|
|
129
|
+
# last argument would end up before the following comma (a newline before it is a
|
|
130
|
+
# syntax error), so it's removed along with the `]`.
|
|
131
|
+
def closing_bracket_range(node, argument)
|
|
132
|
+
return argument.loc.end if argument.equal?(node.last_argument)
|
|
133
|
+
|
|
134
|
+
range_between(argument.children.last.source_range.end_pos, argument.loc.end.end_pos)
|
|
135
|
+
end
|
|
97
136
|
end
|
|
98
137
|
end
|
|
99
138
|
end
|
|
@@ -223,6 +223,10 @@ module RuboCop
|
|
|
223
223
|
}
|
|
224
224
|
PATTERN
|
|
225
225
|
|
|
226
|
+
def self.autocorrect_incompatible_with
|
|
227
|
+
[Style::MethodCallWithArgsParentheses]
|
|
228
|
+
end
|
|
229
|
+
|
|
226
230
|
ASSIGNMENT_TYPES.each do |type|
|
|
227
231
|
define_method :"on_#{type}" do |node|
|
|
228
232
|
return if part_of_ignored_node?(node)
|
|
@@ -33,7 +33,8 @@ module RuboCop
|
|
|
33
33
|
# a `--` trailing justification comment is allowed, so a team can require
|
|
34
34
|
# every disable to be documented instead of banning them outright. Enable
|
|
35
35
|
# directives are not checked in this mode, since they end a suppression
|
|
36
|
-
# rather than start one.
|
|
36
|
+
# rather than start one. Offenses in this mode are not autocorrected,
|
|
37
|
+
# since only a human can supply the missing justification.
|
|
37
38
|
#
|
|
38
39
|
# This cop cannot be disabled via directive comments when it is explicitly
|
|
39
40
|
# enabled with `Enabled: true`. This prevents users from bypassing the cop
|
|
@@ -99,6 +100,8 @@ module RuboCop
|
|
|
99
100
|
'comment are not permitted.'
|
|
100
101
|
|
|
101
102
|
def on_new_investigation
|
|
103
|
+
@matching_pops = nil
|
|
104
|
+
|
|
102
105
|
processed_source.comments.each do |comment|
|
|
103
106
|
directive = DirectiveComment.new(comment)
|
|
104
107
|
next if ignored?(directive)
|
|
@@ -108,7 +111,7 @@ module RuboCop
|
|
|
108
111
|
|
|
109
112
|
next unless disallowed_cops.any?
|
|
110
113
|
|
|
111
|
-
register_offense(
|
|
114
|
+
register_offense(directive, directive_cops, disallowed_cops)
|
|
112
115
|
end
|
|
113
116
|
end
|
|
114
117
|
|
|
@@ -126,19 +129,61 @@ module RuboCop
|
|
|
126
129
|
end
|
|
127
130
|
end
|
|
128
131
|
|
|
129
|
-
def register_offense(
|
|
130
|
-
|
|
131
|
-
replacement = ''
|
|
132
|
+
def register_offense(directive, directive_cops, disallowed_cops)
|
|
133
|
+
comment = directive.comment
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
add_offense(comment, message: offense_message(disallowed_cops)) do |corrector|
|
|
136
|
+
# The remedy in `AllowWithReason` mode is to write the missing `--` justification,
|
|
137
|
+
# which cannot be autocorrected. Removing the directive would also unsuppress
|
|
138
|
+
# the disabled cops and let their autocorrections rewrite the annotated code.
|
|
139
|
+
next if allow_with_reason?
|
|
140
|
+
|
|
141
|
+
if directive_cops.length == disallowed_cops.length
|
|
142
|
+
remove_directive(corrector, directive)
|
|
143
|
+
elsif signed_args?(directive)
|
|
144
|
+
remove_signed_args(corrector, directive, disallowed_cops)
|
|
145
|
+
else
|
|
146
|
+
remove_cops(corrector, comment, disallowed_cops)
|
|
136
147
|
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# A `push` without its `pop` leaves an orphan `pop` behind, so the pair goes together.
|
|
152
|
+
def remove_directive(corrector, directive)
|
|
153
|
+
corrector.replace(directive.comment, '')
|
|
154
|
+
return unless directive.push? && (pop = matching_pop(directive.comment))
|
|
155
|
+
|
|
156
|
+
corrector.replace(pop, '')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def matching_pop(push_comment)
|
|
160
|
+
@matching_pops ||= pair_pushes_with_pops
|
|
161
|
+
@matching_pops[push_comment]
|
|
162
|
+
end
|
|
137
163
|
|
|
138
|
-
|
|
164
|
+
def pair_pushes_with_pops
|
|
165
|
+
pushes = []
|
|
166
|
+
processed_source.comments.each_with_object({}) do |comment, pairs|
|
|
167
|
+
directive = DirectiveComment.new(comment)
|
|
168
|
+
if directive.push?
|
|
169
|
+
pushes.push(comment)
|
|
170
|
+
elsif directive.pop? && (push = pushes.pop)
|
|
171
|
+
pairs[push] = comment
|
|
172
|
+
end
|
|
139
173
|
end
|
|
140
174
|
end
|
|
141
175
|
|
|
176
|
+
def remove_signed_args(corrector, directive, disallowed_cops)
|
|
177
|
+
range = directive.range
|
|
178
|
+
names = Regexp.union(disallowed_cops)
|
|
179
|
+
corrector.replace(range, range.source.gsub(/\s+[+-]#{names}(?!\S)/, ''))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def remove_cops(corrector, comment, disallowed_cops)
|
|
183
|
+
corrector.replace(comment, comment.text.sub(/#{Regexp.union(disallowed_cops)},?\s*/, '')
|
|
184
|
+
.sub(/,\s*$/, ''))
|
|
185
|
+
end
|
|
186
|
+
|
|
142
187
|
def offense_message(disallowed_cops)
|
|
143
188
|
if allow_with_reason?
|
|
144
189
|
MSG_MISSING_REASON
|
|
@@ -150,10 +195,16 @@ module RuboCop
|
|
|
150
195
|
end
|
|
151
196
|
|
|
152
197
|
def directive_cops(directive)
|
|
198
|
+
return directive.signed_args.values.flatten if signed_args?(directive)
|
|
199
|
+
|
|
153
200
|
match_captures = directive.match_captures
|
|
154
201
|
match_captures && match_captures[1] ? match_captures[1].split(',').map(&:strip) : []
|
|
155
202
|
end
|
|
156
203
|
|
|
204
|
+
def signed_args?(directive)
|
|
205
|
+
directive.push? || directive.next?
|
|
206
|
+
end
|
|
207
|
+
|
|
157
208
|
def ignored?(directive)
|
|
158
209
|
return true if exempt_mode?(directive)
|
|
159
210
|
|
|
@@ -169,6 +220,11 @@ module RuboCop
|
|
|
169
220
|
end
|
|
170
221
|
|
|
171
222
|
def allow_with_reason?
|
|
223
|
+
# TODO: The obsolete `AllowTrailingComment` is honored for backward compatibility
|
|
224
|
+
# and will be removed in RuboCop 2.0.
|
|
225
|
+
allow_trailing_comment = cop_config['AllowTrailingComment'] # rubocop:disable InternalAffairs/UndefinedConfig -- obsolete key, gone from default.yml on purpose
|
|
226
|
+
return allow_trailing_comment unless allow_trailing_comment.nil?
|
|
227
|
+
|
|
172
228
|
cop_config['AllowWithReason']
|
|
173
229
|
end
|
|
174
230
|
|
|
@@ -70,6 +70,7 @@ module RuboCop
|
|
|
70
70
|
|
|
71
71
|
def on_send(node)
|
|
72
72
|
return unless double_negative?(node) && node.prefix_bang?
|
|
73
|
+
return if part_of_longer_negation?(node)
|
|
73
74
|
return if style == :allowed_in_returns && allowed_in_returns?(node)
|
|
74
75
|
|
|
75
76
|
location = node.loc.selector
|
|
@@ -81,10 +82,20 @@ module RuboCop
|
|
|
81
82
|
|
|
82
83
|
private
|
|
83
84
|
|
|
85
|
+
def part_of_longer_negation?(node)
|
|
86
|
+
return true if negation?(node.parent) && node.parent.receiver.equal?(node)
|
|
87
|
+
|
|
88
|
+
negation?(node.receiver.receiver)
|
|
89
|
+
end
|
|
90
|
+
|
|
84
91
|
def allowed_in_returns?(node)
|
|
85
92
|
node.parent&.return_type? || end_of_method_definition?(node)
|
|
86
93
|
end
|
|
87
94
|
|
|
95
|
+
def negation?(node)
|
|
96
|
+
node&.send_type? && node.method?(:!)
|
|
97
|
+
end
|
|
98
|
+
|
|
88
99
|
def end_of_method_definition?(node)
|
|
89
100
|
return false unless (def_node = find_def_node_from_ascendant(node))
|
|
90
101
|
|
|
@@ -103,7 +103,7 @@ module RuboCop
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def replacement_range(node)
|
|
106
|
-
if
|
|
106
|
+
if offense_hash_node?(node) && first_argument_unparenthesized?(node)
|
|
107
107
|
# `some_method {}` is not same as `some_method Hash.new`
|
|
108
108
|
# because the braces are interpreted as a block. We will have
|
|
109
109
|
# to rewrite the arguments to wrap them in parenthesis.
|
|
@@ -102,8 +102,10 @@ module RuboCop
|
|
|
102
102
|
compact_style? ? '; ' : "\n#{indent}"
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
# A method with an argument spanning multiple lines (e.g. a hash default
|
|
106
|
+
# value) can't be put on a single line, so it's as compact as it gets.
|
|
105
107
|
def compact?(node)
|
|
106
|
-
node.single_line?
|
|
108
|
+
node.single_line? || node.arguments.any?(&:multiline?)
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
def expanded?(node)
|
|
@@ -131,7 +131,9 @@ module RuboCop
|
|
|
131
131
|
#
|
|
132
132
|
class EndlessMethod < Base
|
|
133
133
|
include ConfigurableEnforcedStyle
|
|
134
|
+
include LineLengthHelp
|
|
134
135
|
include EndlessMethodRewriter
|
|
136
|
+
include EndlessMethodBodyHelp
|
|
135
137
|
extend TargetRubyVersion
|
|
136
138
|
extend AutoCorrector
|
|
137
139
|
|
|
@@ -143,6 +145,12 @@ module RuboCop
|
|
|
143
145
|
MSG_REQUIRE_SINGLE = 'Use endless method definitions for single line methods.'
|
|
144
146
|
MSG_REQUIRE_ALWAYS = 'Use endless method definitions.'
|
|
145
147
|
|
|
148
|
+
# `Style/MethodDefParentheses` adds the same parentheses a parameter list needs in
|
|
149
|
+
# an endless definition, so letting both correct in the same pass would double them.
|
|
150
|
+
def self.autocorrect_incompatible_with
|
|
151
|
+
[Style::MethodCallWithArgsParentheses, Style::MethodDefParentheses]
|
|
152
|
+
end
|
|
153
|
+
|
|
146
154
|
def on_def(node)
|
|
147
155
|
return if node.assignment_method? || use_heredoc?(node)
|
|
148
156
|
|
|
@@ -213,12 +221,26 @@ module RuboCop
|
|
|
213
221
|
return false unless (body = node.body)
|
|
214
222
|
return true if body.any_str_type? && body.heredoc?
|
|
215
223
|
|
|
216
|
-
body.each_descendant(:
|
|
224
|
+
body.each_descendant(:any_str).any?(&:heredoc?)
|
|
217
225
|
end
|
|
218
226
|
|
|
219
227
|
def correct_to_endless(corrector, node)
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
if unparenthesized_arguments?(node)
|
|
229
|
+
corrector.replace(name_to_arguments_range(node), '(')
|
|
230
|
+
corrector.replace(signature_to_body_range(node), ') = ')
|
|
231
|
+
else
|
|
232
|
+
corrector.replace(signature_to_body_range(node), ' = ')
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
corrector.remove(body_to_end_range(node))
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def unparenthesized_arguments?(node)
|
|
239
|
+
node.arguments.any? && !node.arguments.loc.begin
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def name_to_arguments_range(node)
|
|
243
|
+
node.loc.name.end.join(node.arguments.source_range.begin)
|
|
222
244
|
end
|
|
223
245
|
|
|
224
246
|
def signature_to_body_range(node)
|
|
@@ -227,6 +249,10 @@ module RuboCop
|
|
|
227
249
|
signature_end.join(node.body.source_range.begin)
|
|
228
250
|
end
|
|
229
251
|
|
|
252
|
+
def body_to_end_range(node)
|
|
253
|
+
node.body.source_range.end.join(node.loc.end.end)
|
|
254
|
+
end
|
|
255
|
+
|
|
230
256
|
def endless_replacement(node)
|
|
231
257
|
<<~RUBY.strip
|
|
232
258
|
def #{receiver(node)}#{node.method_name}#{arguments(node)} = #{node.body.source}
|
|
@@ -238,11 +264,29 @@ module RuboCop
|
|
|
238
264
|
end
|
|
239
265
|
|
|
240
266
|
def arguments(node, missing = '')
|
|
241
|
-
node.arguments.any?
|
|
267
|
+
return missing unless node.arguments.any?
|
|
268
|
+
return parenthesized_arguments(node) if unparenthesized_arguments?(node)
|
|
269
|
+
|
|
270
|
+
node.arguments.source
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def parenthesized_arguments(node)
|
|
274
|
+
padding = space_inside_parens? ? ' ' : ''
|
|
275
|
+
|
|
276
|
+
"(#{padding}#{node.arguments.source}#{padding})"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def space_inside_parens?
|
|
280
|
+
return false unless config.cop_enabled?('Layout/SpaceInsideParens')
|
|
281
|
+
|
|
282
|
+
config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle'] != 'no_space'
|
|
242
283
|
end
|
|
243
284
|
|
|
244
285
|
def can_be_made_endless?(node)
|
|
245
|
-
|
|
286
|
+
return false unless node.body
|
|
287
|
+
return false if comment_in_discarded_range?(node)
|
|
288
|
+
|
|
289
|
+
endless_method_body?(node.body)
|
|
246
290
|
end
|
|
247
291
|
|
|
248
292
|
def single_line_when_made_endless?(node)
|
|
@@ -252,7 +296,24 @@ module RuboCop
|
|
|
252
296
|
def too_long_when_made_endless?(node)
|
|
253
297
|
return false unless config.cop_enabled?('Layout/LineLength')
|
|
254
298
|
|
|
255
|
-
|
|
299
|
+
line_length(line_when_made_endless(node)) > max_line_length
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def comment_in_discarded_range?(node)
|
|
303
|
+
discarded_ranges = [signature_to_body_range(node), body_to_end_range(node)]
|
|
304
|
+
|
|
305
|
+
processed_source.each_comment_in_lines(node.first_line..node.last_line).any? do |comment|
|
|
306
|
+
discarded_ranges.any? { |range| range.contains?(comment.source_range) }
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def line_when_made_endless(node)
|
|
311
|
+
keyword_element = node.loc.keyword
|
|
312
|
+
code_before = keyword_element.source_line[0...keyword_element.column]
|
|
313
|
+
end_element = node.loc.end
|
|
314
|
+
code_after = end_element.source_line[end_element.last_column..]
|
|
315
|
+
|
|
316
|
+
"#{code_before}#{endless_replacement(node)}#{code_after}"
|
|
256
317
|
end
|
|
257
318
|
end
|
|
258
319
|
end
|
|
@@ -5,11 +5,15 @@ module RuboCop
|
|
|
5
5
|
module Style
|
|
6
6
|
# Checks for exact regexp match inside `Regexp` literals.
|
|
7
7
|
#
|
|
8
|
+
# `=~` returns an index and `match` returns `MatchData`, so they are only
|
|
9
|
+
# autocorrected when their value is used as a condition. The same goes for
|
|
10
|
+
# safe navigation calls, which return `nil` for a `nil` receiver.
|
|
11
|
+
#
|
|
8
12
|
# @example
|
|
9
13
|
#
|
|
10
14
|
# # bad
|
|
11
15
|
# string =~ /\Astring\z/
|
|
12
|
-
#
|
|
16
|
+
# /\Astring\z/ === string
|
|
13
17
|
# string.match(/\Astring\z/)
|
|
14
18
|
# string.match?(/\Astring\z/)
|
|
15
19
|
#
|
|
@@ -27,14 +31,15 @@ module RuboCop
|
|
|
27
31
|
|
|
28
32
|
MSG = 'Use `%<prefer>s`.'
|
|
29
33
|
RESTRICT_ON_SEND = %i[=~ === !~ match match?].freeze
|
|
34
|
+
BOOLEAN_METHODS = %i[=== !~ match?].to_set.freeze
|
|
30
35
|
|
|
36
|
+
# `String#===` is plain equality, so `===` only matches with the regexp as its receiver.
|
|
31
37
|
# @!method exact_regexp_match(node)
|
|
32
38
|
def_node_matcher :exact_regexp_match, <<~PATTERN
|
|
33
|
-
|
|
34
|
-
_ {:=~
|
|
35
|
-
(regexp
|
|
36
|
-
|
|
37
|
-
(regopt)))
|
|
39
|
+
{
|
|
40
|
+
(call _ {:=~ :!~ :match :match?} (regexp (str $_) (regopt)))
|
|
41
|
+
(send (regexp (str $_) (regopt)) :=== _)
|
|
42
|
+
}
|
|
38
43
|
PATTERN
|
|
39
44
|
|
|
40
45
|
def on_send(node)
|
|
@@ -44,16 +49,48 @@ module RuboCop
|
|
|
44
49
|
return unless exact_match_pattern?(parsed_regexp)
|
|
45
50
|
|
|
46
51
|
string = escape_single_quotes(parsed_regexp[1].text)
|
|
47
|
-
|
|
52
|
+
subject = node.method?(:===) ? node.first_argument : receiver
|
|
53
|
+
prefer = "#{subject.source} #{new_method(node)} '#{string}'"
|
|
48
54
|
|
|
49
55
|
add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
|
|
50
|
-
corrector
|
|
56
|
+
autocorrect(corrector, node, prefer)
|
|
51
57
|
end
|
|
52
58
|
end
|
|
53
59
|
alias on_csend on_send
|
|
54
60
|
|
|
55
61
|
private
|
|
56
62
|
|
|
63
|
+
def autocorrect(corrector, node, prefer)
|
|
64
|
+
return unless boolean_result?(node) || truthiness_only?(node)
|
|
65
|
+
|
|
66
|
+
corrector.replace(node, parenthesize?(node) ? "(#{prefer})" : prefer)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# `=~` returns an index, `match` returns `MatchData` and `&.` can return `nil`,
|
|
70
|
+
# so replacing them with `==` is only safe where the value is used as a condition.
|
|
71
|
+
def boolean_result?(node)
|
|
72
|
+
node.send_type? && BOOLEAN_METHODS.include?(node.method_name)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def truthiness_only?(node)
|
|
76
|
+
return true unless node.value_used?
|
|
77
|
+
|
|
78
|
+
parent = node.parent
|
|
79
|
+
if parent.type?(:begin, :and, :or)
|
|
80
|
+
truthiness_only?(parent)
|
|
81
|
+
elsif parent.type?(:if, :while, :until)
|
|
82
|
+
parent.condition.equal?(node)
|
|
83
|
+
else
|
|
84
|
+
parent.send_type? && parent.method?(:!)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parenthesize?(node)
|
|
89
|
+
return false unless (parent = node.parent)&.call_type?
|
|
90
|
+
|
|
91
|
+
parent.receiver.equal?(node) || (parent.operator_method? && !parent.method?(:[]))
|
|
92
|
+
end
|
|
93
|
+
|
|
57
94
|
# Escape characters that are special inside a single-quoted string so the
|
|
58
95
|
# generated literal (e.g. for `/\Afoo'bar\z/`) stays valid Ruby.
|
|
59
96
|
def escape_single_quotes(text)
|