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
|
@@ -50,10 +50,17 @@ module RuboCop
|
|
|
50
50
|
unless when_node.conditions.first.first_line == when_node.conditions.last.last_line
|
|
51
51
|
return true
|
|
52
52
|
end
|
|
53
|
+
return true if ends_with_endless_range?(when_node.conditions.last)
|
|
53
54
|
return false unless when_node.body
|
|
54
55
|
|
|
55
56
|
same_line?(when_node, when_node.body)
|
|
56
57
|
end
|
|
58
|
+
|
|
59
|
+
def ends_with_endless_range?(node)
|
|
60
|
+
node.each_node(:range).any? do |range|
|
|
61
|
+
range.end.nil? && range.source_range.end_pos == node.source_range.end_pos
|
|
62
|
+
end
|
|
63
|
+
end
|
|
57
64
|
end
|
|
58
65
|
end
|
|
59
66
|
end
|
|
@@ -146,6 +146,13 @@ module RuboCop
|
|
|
146
146
|
|
|
147
147
|
MSG = 'Freeze mutable objects assigned to constants.'
|
|
148
148
|
|
|
149
|
+
# With `Recursive: true`, `.freeze` is appended to a nested literal. When the literal
|
|
150
|
+
# is the last element and spans multiple lines, the trailing comma lands between the literal
|
|
151
|
+
# and the `.freeze` appended in the same pass.
|
|
152
|
+
def self.autocorrect_incompatible_with
|
|
153
|
+
[Style::TrailingCommaInArrayLiteral, Style::TrailingCommaInHashLiteral]
|
|
154
|
+
end
|
|
155
|
+
|
|
149
156
|
def on_casgn(node)
|
|
150
157
|
if node.expression.nil? # This is only the case for `CONST += ...` or similar
|
|
151
158
|
parent = node.parent
|
|
@@ -188,10 +195,12 @@ module RuboCop
|
|
|
188
195
|
end
|
|
189
196
|
|
|
190
197
|
def strict_check(value)
|
|
198
|
+
return if value.assignment?
|
|
191
199
|
return if immutable_literal?(value)
|
|
192
200
|
return if operation_produces_immutable_object?(value)
|
|
193
201
|
return if frozen_string_literal?(value)
|
|
194
202
|
return if shareable_constant_value?(value)
|
|
203
|
+
return if parenthesized_immutable_value?(value)
|
|
195
204
|
|
|
196
205
|
true
|
|
197
206
|
end
|
|
@@ -293,12 +302,30 @@ module RuboCop
|
|
|
293
302
|
recent_shareable_value? node
|
|
294
303
|
end
|
|
295
304
|
|
|
305
|
+
def parenthesized_immutable_value?(value)
|
|
306
|
+
return false if !value.begin_type? || !value.children.one?
|
|
307
|
+
|
|
308
|
+
inner = value.children.first
|
|
309
|
+
|
|
310
|
+
!inner.assignment? && !strict_check(inner)
|
|
311
|
+
end
|
|
312
|
+
|
|
296
313
|
def frozen_regexp_or_range_literals?(node)
|
|
297
314
|
target_ruby_version >= 3.0 && node.type?(:regexp, :range)
|
|
298
315
|
end
|
|
299
316
|
|
|
300
317
|
def requires_parentheses?(node)
|
|
301
|
-
node.range_type? ||
|
|
318
|
+
if node.range_type? || node.operator_keyword? || unparenthesized_method_call?(node)
|
|
319
|
+
return true
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
node.if_type? && node.ternary?
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def unparenthesized_method_call?(node)
|
|
326
|
+
return false unless node.call_type?
|
|
327
|
+
|
|
328
|
+
node.loc.dot.nil? || (node.arguments.any? && !node.parenthesized?)
|
|
302
329
|
end
|
|
303
330
|
|
|
304
331
|
def correct_splat_expansion(corrector, expr, splat_value)
|
|
@@ -118,7 +118,8 @@ module RuboCop
|
|
|
118
118
|
if node.ternary?
|
|
119
119
|
node.if_branch
|
|
120
120
|
else
|
|
121
|
-
|
|
121
|
+
if_start = node.loc.begin || node.condition.source_range
|
|
122
|
+
range_between(if_start.end_pos, node.loc.else.begin_pos)
|
|
122
123
|
end
|
|
123
124
|
end
|
|
124
125
|
|
|
@@ -11,6 +11,13 @@ module RuboCop
|
|
|
11
11
|
# and their combinations, allowing safe replacement when the receiver matches.
|
|
12
12
|
# It works with variables, instance variables, class variables, and constants.
|
|
13
13
|
#
|
|
14
|
+
# @safety
|
|
15
|
+
# This cop is unsafe because the receiver may not be an Array. For a Hash,
|
|
16
|
+
# `hash[hash.size - 1]` looks up the key `hash.size - 1`, which `hash[-1]`
|
|
17
|
+
# does not. Even for an Array, `arr[arr.length - n]` wraps around to an
|
|
18
|
+
# element from the end when `n` exceeds the length, while `arr[-n]`
|
|
19
|
+
# returns `nil`.
|
|
20
|
+
#
|
|
14
21
|
# @example
|
|
15
22
|
# # bad
|
|
16
23
|
# arr[arr.count - 2]
|
|
@@ -188,8 +195,9 @@ module RuboCop
|
|
|
188
195
|
return false
|
|
189
196
|
end
|
|
190
197
|
return true if length_receiver.source == array_receiver.source
|
|
198
|
+
return false unless (base_receiver = extract_base_receiver(array_receiver))
|
|
191
199
|
|
|
192
|
-
|
|
200
|
+
base_receiver.source == (extract_base_receiver(length_receiver) || length_receiver).source
|
|
193
201
|
end
|
|
194
202
|
|
|
195
203
|
def receivers_match_strict?(length_receiver, array_receiver)
|
|
@@ -19,6 +19,19 @@ module RuboCop
|
|
|
19
19
|
|
|
20
20
|
MSG = 'Avoid using nested modifiers.'
|
|
21
21
|
|
|
22
|
+
# Operands that would otherwise bind differently next to `&&`/`||`, or
|
|
23
|
+
# not parse at all.
|
|
24
|
+
# @!method low_precedence?(node)
|
|
25
|
+
def_node_matcher :low_precedence?, <<~PATTERN
|
|
26
|
+
{
|
|
27
|
+
[{and or} semantic_operator?]
|
|
28
|
+
[send prefix_not?]
|
|
29
|
+
[if ternary?]
|
|
30
|
+
assignment?
|
|
31
|
+
[{csend yield super} arguments? !parenthesized? !operator_method?]
|
|
32
|
+
}
|
|
33
|
+
PATTERN
|
|
34
|
+
|
|
22
35
|
def on_while(node)
|
|
23
36
|
check(node)
|
|
24
37
|
end
|
|
@@ -63,8 +76,10 @@ module RuboCop
|
|
|
63
76
|
end
|
|
64
77
|
|
|
65
78
|
def left_hand_operand(node, operator)
|
|
66
|
-
expr = node.condition
|
|
67
|
-
|
|
79
|
+
expr = operand_source(node.condition)
|
|
80
|
+
if (node.condition.or_type? && operator == '&&') || low_precedence?(node.condition)
|
|
81
|
+
expr = "(#{expr})"
|
|
82
|
+
end
|
|
68
83
|
expr
|
|
69
84
|
end
|
|
70
85
|
|
|
@@ -72,17 +87,20 @@ module RuboCop
|
|
|
72
87
|
condition = node.condition
|
|
73
88
|
negated = left_hand_keyword != node.keyword
|
|
74
89
|
|
|
75
|
-
expr =
|
|
76
|
-
!condition.operator_method?
|
|
77
|
-
add_parentheses_to_method_arguments(condition)
|
|
78
|
-
else
|
|
79
|
-
condition.source
|
|
80
|
-
end
|
|
90
|
+
expr = operand_source(condition)
|
|
81
91
|
expr = "(#{expr})" if requires_parens?(condition, negated)
|
|
82
92
|
expr = "!#{expr}" if negated
|
|
83
93
|
expr
|
|
84
94
|
end
|
|
85
95
|
|
|
96
|
+
def operand_source(condition)
|
|
97
|
+
if condition.send_type? && !condition.arguments.empty? && !condition.operator_method?
|
|
98
|
+
add_parentheses_to_method_arguments(condition)
|
|
99
|
+
else
|
|
100
|
+
condition.source
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
86
104
|
def add_parentheses_to_method_arguments(send_node)
|
|
87
105
|
expr = +''
|
|
88
106
|
expr << "#{send_node.receiver.source}." if send_node.receiver
|
|
@@ -97,6 +115,7 @@ module RuboCop
|
|
|
97
115
|
# whole expression (e.g. `!(a && b)`, not `!a && b`).
|
|
98
116
|
(negated && node.operator_keyword?) ||
|
|
99
117
|
node.or_type? ||
|
|
118
|
+
low_precedence?(node) ||
|
|
100
119
|
!(RuboCop::AST::Node::COMPARISON_OPERATORS & node.children).empty?
|
|
101
120
|
end
|
|
102
121
|
end
|
|
@@ -26,7 +26,7 @@ module RuboCop
|
|
|
26
26
|
|
|
27
27
|
node.each_descendant(:if).select(&:ternary?).each do |nested_ternary|
|
|
28
28
|
add_offense(nested_ternary) do |corrector|
|
|
29
|
-
next if part_of_ignored_node?(node)
|
|
29
|
+
next if part_of_ignored_node?(node) || contains_heredoc?(node)
|
|
30
30
|
|
|
31
31
|
autocorrect(corrector, node)
|
|
32
32
|
ignore_node(node)
|
|
@@ -36,21 +36,39 @@ module RuboCop
|
|
|
36
36
|
|
|
37
37
|
private
|
|
38
38
|
|
|
39
|
+
def contains_heredoc?(node)
|
|
40
|
+
node.each_descendant(:any_str).any?(&:heredoc?)
|
|
41
|
+
end
|
|
42
|
+
|
|
39
43
|
def autocorrect(corrector, if_node)
|
|
40
44
|
replace_loc_and_whitespace(corrector, if_node.loc.question, "\n")
|
|
41
45
|
replace_loc_and_whitespace(corrector, if_node.loc.colon, "\nelse\n")
|
|
42
|
-
corrector
|
|
43
|
-
corrector.wrap(if_node, 'if ', "\nend")
|
|
44
|
-
end
|
|
46
|
+
remove_parentheses(corrector, if_node.if_branch)
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
source.delete_prefix('(').delete_suffix(')')
|
|
48
|
+
if modifier_position?(if_node)
|
|
49
|
+
corrector.wrap(if_node, '(if ', "\nend)")
|
|
49
50
|
else
|
|
50
|
-
|
|
51
|
+
corrector.wrap(if_node, 'if ', "\nend")
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
def remove_parentheses(corrector, node)
|
|
56
|
+
return unless node.begin_type? && node.parenthesized_call?
|
|
57
|
+
|
|
58
|
+
corrector.remove(node.loc.begin)
|
|
59
|
+
corrector.remove(node.loc.end)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# An `if` right after `return`, as the operand of `not` or `defined?`, or as
|
|
63
|
+
# the first argument of a call without parentheses would be parsed as a modifier.
|
|
64
|
+
def modifier_position?(node)
|
|
65
|
+
return false unless (parent = node.parent)
|
|
66
|
+
return true if parent.type?(:return, :break, :next)
|
|
67
|
+
return false if !parent.type?(:call, :super, :yield, :defined?) || parent.parenthesized?
|
|
68
|
+
|
|
69
|
+
parent.prefix_not? || parent.first_argument.equal?(node)
|
|
70
|
+
end
|
|
71
|
+
|
|
54
72
|
def replace_loc_and_whitespace(corrector, range, replacement)
|
|
55
73
|
corrector.replace(
|
|
56
74
|
range_with_surrounding_space(range: range, whitespace: true),
|
|
@@ -181,11 +181,11 @@ module RuboCop
|
|
|
181
181
|
def autocorrect_modifier(corrector, node)
|
|
182
182
|
body = node.if_branch || node.else_branch
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"#{' ' * node.source_range.column}#{body.source}"
|
|
184
|
+
next_code = "next #{node.inverse_keyword} #{node.condition.source}\n" \
|
|
185
|
+
"#{' ' * node.source_range.column}"
|
|
187
186
|
|
|
188
|
-
corrector.
|
|
187
|
+
corrector.insert_before(node, next_code)
|
|
188
|
+
corrector.remove(range_between(body.source_range.end_pos, node.source_range.end_pos))
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
def autocorrect_block(corrector, node)
|
|
@@ -231,7 +231,7 @@ module RuboCop
|
|
|
231
231
|
|
|
232
232
|
# end_range starts with the final newline of the if body
|
|
233
233
|
lines = (node.source_range.line + 1)...node.loc.end.line
|
|
234
|
-
lines = lines.to_a -
|
|
234
|
+
lines = lines.to_a - string_content_lines(node)
|
|
235
235
|
# Skip blank lines
|
|
236
236
|
lines.reject { |lineno| /\A\s*\z/.match?(buffer.source_line(lineno)) }
|
|
237
237
|
end
|
|
@@ -250,11 +250,25 @@ module RuboCop
|
|
|
250
250
|
lines.map { |lineno| buffer.source_line(lineno) =~ /\S/ }.min
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
# Lines that start inside a string literal or a heredoc body, whose
|
|
254
|
+
# leading whitespace is part of the string value.
|
|
255
|
+
def string_content_lines(node)
|
|
256
|
+
buffer = node.source_range.source_buffer
|
|
257
|
+
ranges = node.each_node(:any_str).filter_map { |str| string_content_range(str) }
|
|
258
|
+
|
|
259
|
+
ranges.flat_map do |range|
|
|
260
|
+
(range.line..range.last_line).select do |lineno|
|
|
261
|
+
(range.begin_pos...range.end_pos).cover?(buffer.line_range(lineno).begin_pos)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def string_content_range(node)
|
|
267
|
+
if node.heredoc?
|
|
268
|
+
node.loc.heredoc_body
|
|
269
|
+
elsif node.loc?(:begin) && node.loc?(:end)
|
|
270
|
+
node.loc.begin.end.join(node.loc.end.begin)
|
|
271
|
+
end
|
|
258
272
|
end
|
|
259
273
|
|
|
260
274
|
def reindent_line(corrector, lineno, delta, buffer)
|
|
@@ -44,6 +44,7 @@ module RuboCop
|
|
|
44
44
|
|
|
45
45
|
def on_send(node)
|
|
46
46
|
return unless node.receiver
|
|
47
|
+
return if prefer_comparison? && comment_before_dot?(node)
|
|
47
48
|
|
|
48
49
|
style_check?(node) do
|
|
49
50
|
add_offense(node.loc.selector) do |corrector|
|
|
@@ -58,12 +59,19 @@ module RuboCop
|
|
|
58
59
|
|
|
59
60
|
private
|
|
60
61
|
|
|
62
|
+
def comment_before_dot?(node)
|
|
63
|
+
return false unless node.loc.dot
|
|
64
|
+
|
|
65
|
+
processed_source.contains_comment?(node.receiver.source_range.end.join(node.loc.dot))
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
def autocorrect_to_comparison(corrector, node)
|
|
62
|
-
range = node.
|
|
69
|
+
range = node.receiver.source_range.end.join(node.loc.selector.end)
|
|
63
70
|
corrector.replace(range, ' == nil')
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
|
|
72
|
+
return if !operator_expression?(node.parent) && !receiver_of_parent?(node)
|
|
73
|
+
|
|
74
|
+
corrector.wrap(node, '(', ')')
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
def autocorrect_to_predicate(corrector, node)
|
|
@@ -88,6 +96,10 @@ module RuboCop
|
|
|
88
96
|
node.assignment?
|
|
89
97
|
end
|
|
90
98
|
|
|
99
|
+
def receiver_of_parent?(node)
|
|
100
|
+
node.parent&.call_type? && node.parent.receiver.equal?(node)
|
|
101
|
+
end
|
|
102
|
+
|
|
91
103
|
def operator_send?(node)
|
|
92
104
|
node.send_type? && node.operator_method? && !node.method?(:[]) && !node.method?(:[]=)
|
|
93
105
|
end
|
|
@@ -130,7 +130,7 @@ module RuboCop
|
|
|
130
130
|
|
|
131
131
|
def operator_expression?(node)
|
|
132
132
|
node.operator_keyword? ||
|
|
133
|
-
(node.send_type? && node.
|
|
133
|
+
(node.send_type? && node.operator_method?) ||
|
|
134
134
|
(node.if_type? && node.ternary?) ||
|
|
135
135
|
node.type?(:range, :iflipflop, :eflipflop) ||
|
|
136
136
|
node.assignment?
|
|
@@ -54,11 +54,10 @@ module RuboCop
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def requires_parens?(child)
|
|
57
|
-
child.operator_keyword? ||
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
child.assignment?
|
|
57
|
+
return true if child.operator_keyword? || operator_method_requiring_parens?(child)
|
|
58
|
+
return true if child.if_type? && child.ternary?
|
|
59
|
+
|
|
60
|
+
child.type?(:range, :iflipflop, :eflipflop) || child.assignment?
|
|
62
61
|
end
|
|
63
62
|
|
|
64
63
|
def correct_opposite_method(corrector, range, child)
|
|
@@ -74,6 +73,12 @@ module RuboCop
|
|
|
74
73
|
def correct_without_parens(corrector, range)
|
|
75
74
|
corrector.replace(range, '!')
|
|
76
75
|
end
|
|
76
|
+
|
|
77
|
+
def operator_method_requiring_parens?(child)
|
|
78
|
+
return false unless child.send_type?
|
|
79
|
+
|
|
80
|
+
child.binary_operation? || (child.unary_operation? && child.method?(:~))
|
|
81
|
+
end
|
|
77
82
|
end
|
|
78
83
|
end
|
|
79
84
|
end
|
|
@@ -90,6 +90,7 @@ module RuboCop
|
|
|
90
90
|
def on_send(node)
|
|
91
91
|
numeric, replacement = check(node)
|
|
92
92
|
return unless numeric
|
|
93
|
+
return if handled_by_bitwise_predicate?(node)
|
|
93
94
|
|
|
94
95
|
return if allowed_method_name?(node.method_name) ||
|
|
95
96
|
node.each_ancestor(:send, :any_block).any? do |ancestor|
|
|
@@ -108,6 +109,12 @@ module RuboCop
|
|
|
108
109
|
allowed_method?(name) || matches_allowed_pattern?(name)
|
|
109
110
|
end
|
|
110
111
|
|
|
112
|
+
def handled_by_bitwise_predicate?(node)
|
|
113
|
+
bitwise_predicate_candidate?(node) &&
|
|
114
|
+
config.for_cop('Style/BitwisePredicate').fetch('Enabled') &&
|
|
115
|
+
BitwisePredicate.support_target_ruby_version?(target_ruby_version)
|
|
116
|
+
end
|
|
117
|
+
|
|
111
118
|
def check(node)
|
|
112
119
|
numeric, operator =
|
|
113
120
|
if style == :predicate
|
|
@@ -124,7 +131,7 @@ module RuboCop
|
|
|
124
131
|
def replacement(node, numeric, operation)
|
|
125
132
|
if style == :predicate
|
|
126
133
|
[parenthesized_source(numeric), REPLACEMENTS.invert[operation.to_s]].join('.')
|
|
127
|
-
elsif
|
|
134
|
+
elsif comparison_requires_parentheses?(node)
|
|
128
135
|
"(#{numeric.source} #{REPLACEMENTS[operation.to_s]} 0)"
|
|
129
136
|
else
|
|
130
137
|
[numeric.source, REPLACEMENTS[operation.to_s], 0].join(' ')
|
|
@@ -140,7 +147,7 @@ module RuboCop
|
|
|
140
147
|
end
|
|
141
148
|
|
|
142
149
|
def require_parentheses?(node)
|
|
143
|
-
node.send_type? && node.
|
|
150
|
+
node.send_type? && node.operator_method? && !node.parenthesized?
|
|
144
151
|
end
|
|
145
152
|
|
|
146
153
|
def replacement_supported?(operator)
|
|
@@ -159,10 +166,11 @@ module RuboCop
|
|
|
159
166
|
end
|
|
160
167
|
end
|
|
161
168
|
|
|
162
|
-
def
|
|
163
|
-
return false unless (parent = node.parent)
|
|
169
|
+
def comparison_requires_parentheses?(node)
|
|
170
|
+
return false unless (parent = node.parent)&.call_type?
|
|
171
|
+
return true if parent.receiver.equal?(node)
|
|
164
172
|
|
|
165
|
-
parent.
|
|
173
|
+
parent.operator_method? && !parent.method?(:[]) && !parent.method?(:[]=)
|
|
166
174
|
end
|
|
167
175
|
|
|
168
176
|
# @!method predicate(node)
|
|
@@ -179,6 +187,14 @@ module RuboCop
|
|
|
179
187
|
def_node_matcher :inverted_comparison, <<~PATTERN
|
|
180
188
|
(send (int 0) ${:== :> :<} [$(...) !gvar_type?])
|
|
181
189
|
PATTERN
|
|
190
|
+
|
|
191
|
+
# @!method bitwise_predicate_candidate?(node)
|
|
192
|
+
def_node_matcher :bitwise_predicate_candidate?, <<~PATTERN
|
|
193
|
+
{
|
|
194
|
+
(send {(begin (send _ :& _)) (send _ :& _)} {:positive? :zero?})
|
|
195
|
+
(send {(begin (send _ :& _)) (send _ :& _)} {:> :==} (int 0))
|
|
196
|
+
}
|
|
197
|
+
PATTERN
|
|
182
198
|
end
|
|
183
199
|
end
|
|
184
200
|
end
|
|
@@ -102,6 +102,7 @@ module RuboCop
|
|
|
102
102
|
return replaced_node unless node.parent
|
|
103
103
|
return "(#{replaced_node})" if node.parent.operator_keyword?
|
|
104
104
|
return "(#{replaced_node})" if node.parent.send_type? && node.parent.operator_method?
|
|
105
|
+
return "(#{replaced_node})" if parent_binds_tighter_than_ternary?(node)
|
|
105
106
|
|
|
106
107
|
replaced_node
|
|
107
108
|
end
|
|
@@ -124,6 +125,14 @@ module RuboCop
|
|
|
124
125
|
"#{expr_replacement(else_branch)}"
|
|
125
126
|
end
|
|
126
127
|
|
|
128
|
+
def parent_binds_tighter_than_ternary?(node)
|
|
129
|
+
parent = node.parent
|
|
130
|
+
return parent.receiver.equal?(node) if parent.call_type?
|
|
131
|
+
return parent.namespace.equal?(node) if parent.type?(:const, :casgn)
|
|
132
|
+
|
|
133
|
+
parent.if_type? && parent.ternary? && parent.condition.equal?(node)
|
|
134
|
+
end
|
|
135
|
+
|
|
127
136
|
def expr_replacement(node)
|
|
128
137
|
return 'nil' if node.nil?
|
|
129
138
|
|
|
@@ -131,7 +140,9 @@ module RuboCop
|
|
|
131
140
|
end
|
|
132
141
|
|
|
133
142
|
def requires_parentheses?(node)
|
|
134
|
-
return true if
|
|
143
|
+
return true if node.type?(
|
|
144
|
+
:and, :or, :if, :rescue, :while, :until, :while_post, :until_post, :any_match_pattern
|
|
145
|
+
)
|
|
135
146
|
return true if node.assignment?
|
|
136
147
|
return true if method_call_with_changed_precedence?(node)
|
|
137
148
|
|
|
@@ -139,7 +150,7 @@ module RuboCop
|
|
|
139
150
|
end
|
|
140
151
|
|
|
141
152
|
def method_call_with_changed_precedence?(node)
|
|
142
|
-
return false unless node.
|
|
153
|
+
return false unless node.call_type? && node.arguments?
|
|
143
154
|
return false if node.parenthesized_call?
|
|
144
155
|
|
|
145
156
|
!node.operator_method?
|
|
@@ -86,10 +86,21 @@ module RuboCop
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def include_same_character_as_used_for_delimiter?(node, type)
|
|
89
|
-
return false unless %w[%w %i].include?(type)
|
|
90
|
-
|
|
91
89
|
used_delimiters = matchpairs(begin_source(node)[-1])
|
|
92
|
-
contains_delimiter?(node, used_delimiters)
|
|
90
|
+
return contains_delimiter?(node, used_delimiters) if %w[%w %i].include?(type)
|
|
91
|
+
return false unless %w[%q %s %r].include?(type)
|
|
92
|
+
|
|
93
|
+
contains_escaped_delimiter?(node, used_delimiters)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Without interpolation an escaped delimiter means something different
|
|
97
|
+
# once the delimiters change (`%q!it\!s!` is `"it!s"`, but `%q(it\!s)`
|
|
98
|
+
# is `"it\\!s"`). For a regexp the match stays the same, but its source
|
|
99
|
+
# changes. Interpolating literals read `\!` as `!` with any delimiter.
|
|
100
|
+
def contains_escaped_delimiter?(node, delimiters)
|
|
101
|
+
body = node.loc.begin.end.join(node.loc.end.begin).source
|
|
102
|
+
|
|
103
|
+
body.scan(/\\./m).any? { |escape| delimiters.include?(escape[1]) }
|
|
93
104
|
end
|
|
94
105
|
|
|
95
106
|
def contains_delimiter?(node, delimiters)
|
|
@@ -183,7 +183,7 @@ module RuboCop
|
|
|
183
183
|
end
|
|
184
184
|
|
|
185
185
|
def adjusted_source(expr_node, block_node)
|
|
186
|
-
source = expr_node
|
|
186
|
+
source = element_source(expr_node)
|
|
187
187
|
return source unless block_node.numblock_type?
|
|
188
188
|
return source if block_node.method?(:each_with_object)
|
|
189
189
|
|
|
@@ -194,6 +194,22 @@ module RuboCop
|
|
|
194
194
|
def indent(node)
|
|
195
195
|
' ' * node.source_range.column
|
|
196
196
|
end
|
|
197
|
+
|
|
198
|
+
def element_source(expr_node)
|
|
199
|
+
if command_call?(expr_node) || expr_node.assignment? || expr_node.rescue_type?
|
|
200
|
+
return "(#{expr_node.source})"
|
|
201
|
+
end
|
|
202
|
+
return "[#{expr_node.source}]" if expr_node.array_type? && !expr_node.bracketed?
|
|
203
|
+
|
|
204
|
+
expr_node.source
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def command_call?(node)
|
|
208
|
+
node = node.send_node if node.any_block_type?
|
|
209
|
+
return false unless node.type?(:call, :super, :yield)
|
|
210
|
+
|
|
211
|
+
node.arguments? && !node.parenthesized? && !node.operator_method? && !node.setter_method?
|
|
212
|
+
end
|
|
197
213
|
end
|
|
198
214
|
end
|
|
199
215
|
end
|
|
@@ -139,7 +139,7 @@ module RuboCop
|
|
|
139
139
|
offense_range = node.loc.begin
|
|
140
140
|
|
|
141
141
|
add_offense(offense_range) do |corrector|
|
|
142
|
-
if node.parent&.assignment?
|
|
142
|
+
if node.parent&.assignment? || node.parent&.type?(:return, :next, :break)
|
|
143
143
|
replace_begin_with_statement(corrector, offense_range, node)
|
|
144
144
|
else
|
|
145
145
|
remove_begin(corrector, offense_range, node)
|
|
@@ -156,7 +156,7 @@ module RuboCop
|
|
|
156
156
|
first_child = node.children.first
|
|
157
157
|
|
|
158
158
|
source = first_child.source
|
|
159
|
-
source = "(#{source})" if
|
|
159
|
+
source = "(#{source})" if low_precedence_statement?(first_child)
|
|
160
160
|
|
|
161
161
|
corrector.replace(offense_range, source)
|
|
162
162
|
corrector.remove(range_between(offense_range.end_pos, first_child.source_range.end_pos))
|
|
@@ -164,6 +164,13 @@ module RuboCop
|
|
|
164
164
|
restore_removed_comments(corrector, offense_range, node, first_child)
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
+
def low_precedence_statement?(node)
|
|
168
|
+
return node.modifier_form? if node.basic_conditional?
|
|
169
|
+
return node.prefix_not? if node.send_type?
|
|
170
|
+
|
|
171
|
+
node.operator_keyword? && node.semantic_operator?
|
|
172
|
+
end
|
|
173
|
+
|
|
167
174
|
def remove_begin(corrector, offense_range, node)
|
|
168
175
|
if node.parent.respond_to?(:endless?) && node.parent.endless?
|
|
169
176
|
offense_range = range_with_surrounding_space(offense_range, newlines: true)
|
|
@@ -222,7 +229,17 @@ module RuboCop
|
|
|
222
229
|
end
|
|
223
230
|
|
|
224
231
|
def valid_begin_assignment?(node)
|
|
225
|
-
node.parent&.assignment? && !node.children.one?
|
|
232
|
+
node.parent&.assignment? && (!node.children.one? || too_long_when_joined?(node))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def too_long_when_joined?(node)
|
|
236
|
+
statement = node.children.first
|
|
237
|
+
max = config.for_enabled_cop('Layout/LineLength')['Max']
|
|
238
|
+
return false unless max && statement.single_line?
|
|
239
|
+
|
|
240
|
+
length = statement.source.length
|
|
241
|
+
length += 2 if low_precedence_statement?(statement)
|
|
242
|
+
node.loc.column + length > max
|
|
226
243
|
end
|
|
227
244
|
|
|
228
245
|
def inspect_branches(node)
|