sonicop 26.8.111 → 26.8.113
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/CONFORMANCE.md +64 -31
- data/Cargo.lock +94 -19
- data/Cargo.toml +13 -8
- data/NOTICE +15 -3
- data/README.ja.md +145 -57
- data/README.md +140 -48
- data/build.rs +155 -0
- data/lib/sonicop/runner.rb +7 -1
- data/lib/sonicop/version.rb +1 -1
- data/src/cli.rs +216 -45
- data/src/config/inheritance.rs +637 -64
- data/src/config/mod.rs +130 -1
- data/src/config/paths.rs +550 -48
- data/src/cop_name.rs +18 -1
- data/src/diagnostic.rs +168 -3
- data/src/directives.rs +398 -77
- data/src/display_width.rs +82 -42
- data/src/display_width_table.rs +506 -0
- data/src/engine.rs +1532 -51
- data/src/formatter.rs +10 -1
- data/src/lib.rs +2 -0
- data/src/main.rs +112 -1
- data/src/nul_bytes.rs +245 -47
- data/src/parser.rs +37 -0
- data/src/profile.rs +174 -24
- data/src/ruby_version.rs +167 -21
- data/src/rules/bundler/duplicated_group.rs +3 -2
- data/src/rules/bundler/gem_comment.rs +5 -4
- data/src/rules/gemspec/attribute_assignment.rs +4 -3
- data/src/rules/gemspec/deprecated_attribute_assignment.rs +7 -6
- data/src/rules/gemspec/duplicated_assignment.rs +4 -3
- data/src/rules/gemspec/require_mfa.rs +10 -9
- data/src/rules/gemspec/required_ruby_version.rs +3 -2
- data/src/rules/gemspec/support.rs +28 -8
- data/src/rules/layout/access_modifier_indentation.rs +13 -6
- data/src/rules/layout/argument_alignment.rs +19 -7
- data/src/rules/layout/array_alignment.rs +1 -2
- data/src/rules/layout/block_alignment.rs +74 -30
- data/src/rules/layout/block_end_newline.rs +5 -5
- data/src/rules/layout/class_structure.rs +13 -4
- data/src/rules/layout/closing_heredoc_indentation.rs +24 -5
- data/src/rules/layout/closing_parenthesis_indentation.rs +3 -3
- data/src/rules/layout/comment_indentation.rs +2 -1
- data/src/rules/layout/condition_position.rs +3 -2
- data/src/rules/layout/def_end_alignment.rs +10 -6
- data/src/rules/layout/dot_position.rs +1 -2
- data/src/rules/layout/else_alignment.rs +24 -14
- data/src/rules/layout/empty_line_after_guard_clause.rs +8 -7
- data/src/rules/layout/empty_line_after_multiline_condition.rs +5 -5
- data/src/rules/layout/empty_line_between_defs.rs +8 -6
- data/src/rules/layout/empty_lines_around_access_modifier.rs +22 -10
- data/src/rules/layout/empty_lines_around_arguments.rs +4 -4
- data/src/rules/layout/empty_lines_around_body.rs +8 -2
- data/src/rules/layout/empty_lines_around_exception_handling_keywords.rs +1 -2
- data/src/rules/layout/empty_lines_around_method_body.rs +3 -2
- data/src/rules/layout/end_alignment.rs +32 -7
- data/src/rules/layout/end_of_line.rs +14 -1
- data/src/rules/layout/extra_spacing.rs +9 -11
- data/src/rules/layout/first_array_element_indentation.rs +1 -2
- data/src/rules/layout/first_hash_element_indentation.rs +8 -6
- data/src/rules/layout/hash_alignment.rs +113 -19
- data/src/rules/layout/heredoc_argument_closing_parenthesis.rs +6 -5
- data/src/rules/layout/heredoc_indentation.rs +4 -3
- data/src/rules/layout/indentation_style.rs +6 -4
- data/src/rules/layout/indentation_width.rs +76 -16
- data/src/rules/layout/initial_indentation.rs +18 -5
- data/src/rules/layout/line_continuation_leading_space.rs +4 -3
- data/src/rules/layout/line_continuation_spacing.rs +4 -3
- data/src/rules/layout/line_length.rs +417 -43
- data/src/rules/layout/mod.rs +1 -1
- data/src/rules/layout/multiline_assignment_layout.rs +20 -3
- data/src/rules/layout/multiline_block_layout.rs +1 -2
- data/src/rules/layout/multiline_expression.rs +60 -31
- data/src/rules/layout/multiline_hash_brace_layout.rs +4 -3
- data/src/rules/layout/multiline_method_call_indentation.rs +1 -1
- data/src/rules/layout/multiline_operation_indentation.rs +3 -3
- data/src/rules/layout/redundant_line_break.rs +16 -5
- data/src/rules/layout/rescue_ensure_alignment.rs +20 -12
- data/src/rules/layout/single_line_block_chain.rs +13 -4
- data/src/rules/layout/space_around_block_parameters.rs +16 -2
- data/src/rules/layout/space_around_equals_in_parameter_default.rs +1 -2
- data/src/rules/layout/space_around_keyword.rs +22 -4
- data/src/rules/layout/space_around_operators.rs +34 -2
- data/src/rules/layout/space_before_brackets.rs +13 -6
- data/src/rules/layout/space_before_comma.rs +3 -2
- data/src/rules/layout/space_inside_array_literal_brackets.rs +25 -10
- data/src/rules/layout/space_inside_array_percent_literal.rs +7 -1
- data/src/rules/layout/space_inside_parens.rs +170 -87
- data/src/rules/layout/space_inside_range_literal.rs +53 -19
- data/src/rules/layout/space_inside_reference_brackets.rs +65 -3
- data/src/rules/layout/space_inside_string_interpolation.rs +147 -48
- data/src/rules/layout/support.rs +172 -26
- data/src/rules/layout/tokens.rs +47 -7
- data/src/rules/layout/trailing_whitespace.rs +5 -2
- data/src/rules/lint/access_modifier.rs +50 -5
- data/src/rules/lint/ambiguity.rs +97 -7
- data/src/rules/lint/ambiguous_block_association.rs +29 -12
- data/src/rules/lint/ambiguous_operator.rs +8 -0
- data/src/rules/lint/ambiguous_operator_precedence.rs +18 -1
- data/src/rules/lint/ambiguous_range.rs +15 -1
- data/src/rules/lint/ambiguous_regexp_literal.rs +40 -1
- data/src/rules/lint/array_literal_in_regexp.rs +13 -5
- data/src/rules/lint/assignment_in_condition.rs +3 -2
- data/src/rules/lint/blocks.rs +2 -1
- data/src/rules/lint/boolean_symbol.rs +4 -4
- data/src/rules/lint/constant_definition_in_block.rs +1 -2
- data/src/rules/lint/constant_reassignment.rs +9 -13
- data/src/rules/lint/constant_resolution.rs +8 -0
- data/src/rules/lint/cop_directives.rs +28 -4
- data/src/rules/lint/debugger.rs +59 -9
- data/src/rules/lint/deprecated_open_ssl_constant.rs +1 -2
- data/src/rules/lint/disjunctive_assignment_in_constructor.rs +2 -2
- data/src/rules/lint/duplicate_branch.rs +16 -6
- data/src/rules/lint/duplicate_case_condition.rs +5 -5
- data/src/rules/lint/duplicate_hash_key.rs +8 -4
- data/src/rules/lint/duplicate_match_pattern.rs +7 -5
- data/src/rules/lint/duplicate_methods.rs +133 -54
- data/src/rules/lint/duplicate_require.rs +1 -2
- data/src/rules/lint/duplicate_set_element.rs +19 -5
- data/src/rules/lint/empty_conditional_body.rs +19 -3
- data/src/rules/lint/empty_expression.rs +16 -1
- data/src/rules/lint/empty_in_pattern.rs +3 -2
- data/src/rules/lint/empty_when.rs +4 -4
- data/src/rules/lint/ensure_return.rs +3 -2
- data/src/rules/lint/float_comparison.rs +8 -6
- data/src/rules/lint/flow.rs +8 -5
- data/src/rules/lint/format_parameter_mismatch.rs +4 -3
- data/src/rules/lint/hash_compare_by_identity.rs +5 -4
- data/src/rules/lint/heredoc_method_call_position.rs +3 -2
- data/src/rules/lint/implicit_string_concatenation.rs +33 -3
- data/src/rules/lint/ineffective_access_modifier.rs +5 -4
- data/src/rules/lint/inherit_exception.rs +4 -3
- data/src/rules/lint/literal_as_condition.rs +51 -31
- data/src/rules/lint/literal_assignment_in_condition.rs +4 -3
- data/src/rules/lint/literal_in_interpolation.rs +66 -5
- data/src/rules/lint/literals.rs +10 -8
- data/src/rules/lint/missing_cop_enable_directive.rs +95 -18
- data/src/rules/lint/mod.rs +1 -0
- data/src/rules/lint/nested_method_definition.rs +36 -11
- data/src/rules/lint/nil_receiver.rs +420 -0
- data/src/rules/lint/no_return_in_begin_end_blocks.rs +2 -2
- data/src/rules/lint/node_equality.rs +8 -5
- data/src/rules/lint/non_atomic_file_operation.rs +9 -3
- data/src/rules/lint/non_deterministic_require_order.rs +5 -3
- data/src/rules/lint/non_local_exit_from_iterator.rs +22 -9
- data/src/rules/lint/number_conversion.rs +1 -1
- data/src/rules/lint/out_of_range_regexp_ref.rs +6 -4
- data/src/rules/lint/parentheses_as_grouped_expression.rs +5 -0
- data/src/rules/lint/redundant_cop_enable_directive.rs +3 -1
- data/src/rules/lint/redundant_dir_glob_sort.rs +1 -2
- data/src/rules/lint/redundant_safe_navigation.rs +47 -22
- data/src/rules/lint/redundant_splat_expansion.rs +4 -2
- data/src/rules/lint/redundant_string_coercion.rs +3 -2
- data/src/rules/lint/redundant_type_conversion.rs +16 -4
- data/src/rules/lint/regexp.rs +1 -3
- data/src/rules/lint/regexp_source.rs +2 -2
- data/src/rules/lint/regexp_tree.rs +38 -5
- data/src/rules/lint/require_range_parentheses.rs +5 -5
- data/src/rules/lint/rescue_exception.rs +4 -3
- data/src/rules/lint/rescue_type.rs +3 -2
- data/src/rules/lint/return_in_void_context.rs +8 -3
- data/src/rules/lint/safe_navigation_chain.rs +47 -11
- data/src/rules/lint/self_assignment.rs +20 -2
- data/src/rules/lint/shadowed_argument.rs +2 -2
- data/src/rules/lint/shadowed_exception.rs +2 -2
- data/src/rules/lint/suppressed_exception.rs +21 -3
- data/src/rules/lint/suppressed_exception_in_number_conversion.rs +4 -3
- data/src/rules/lint/symbol_conversion.rs +56 -8
- data/src/rules/lint/syntax.rs +195 -5
- data/src/rules/lint/top_level_return_with_argument.rs +11 -1
- data/src/rules/lint/triple_quotes.rs +3 -2
- data/src/rules/lint/unescaped_bracket_in_regexp.rs +1 -1
- data/src/rules/lint/unexpected_block_arity.rs +23 -5
- data/src/rules/lint/unified_integer.rs +18 -12
- data/src/rules/lint/unmodified_reduce_accumulator.rs +36 -7
- data/src/rules/lint/unreachable_code.rs +4 -4
- data/src/rules/lint/unreachable_loop.rs +26 -10
- data/src/rules/lint/unreachable_pattern_branch.rs +7 -5
- data/src/rules/lint/unused_block_argument.rs +2 -2
- data/src/rules/lint/unused_method_argument.rs +1 -2
- data/src/rules/lint/useless_assignment.rs +3 -2
- data/src/rules/lint/useless_constant_scoping.rs +3 -2
- data/src/rules/lint/useless_else_without_rescue.rs +4 -3
- data/src/rules/lint/useless_rescue.rs +5 -4
- data/src/rules/lint/useless_ruby2_keywords.rs +2 -1
- data/src/rules/lint/useless_setter_call.rs +5 -4
- data/src/rules/lint/useless_times.rs +4 -3
- data/src/rules/lint/variable_force.rs +307 -79
- data/src/rules/lint/void.rs +32 -20
- data/src/rules/metrics/abc_size.rs +197 -7
- data/src/rules/metrics/block_length.rs +19 -10
- data/src/rules/metrics/block_nesting.rs +1 -2
- data/src/rules/metrics/class_length.rs +16 -4
- data/src/rules/metrics/collection_literal_length.rs +2 -1
- data/src/rules/metrics/complexity.rs +52 -14
- data/src/rules/metrics/fragments.rs +10 -14
- data/src/rules/metrics/locals.rs +35 -14
- data/src/rules/metrics/method_length.rs +14 -6
- data/src/rules/metrics/parameter_lists.rs +7 -6
- data/src/rules/metrics/perceived_complexity.rs +1 -3
- data/src/rules/metrics/support.rs +94 -4
- data/src/rules/mod.rs +525 -80
- data/src/rules/naming/ascii_identifiers.rs +3 -3
- data/src/rules/naming/binary_operator_parameter_name.rs +5 -4
- data/src/rules/naming/block_forwarding.rs +9 -8
- data/src/rules/naming/constant_name.rs +56 -18
- data/src/rules/naming/file_name.rs +174 -43
- data/src/rules/naming/method_name.rs +8 -1
- data/src/rules/naming/predicate_method.rs +42 -14
- data/src/rules/naming/predicate_prefix.rs +70 -4
- data/src/rules/naming/rescued_exceptions_variable_name.rs +14 -11
- data/src/rules/naming/support.rs +54 -295
- data/src/rules/naming/variable_name.rs +17 -4
- data/src/rules/naming/variable_number.rs +9 -10
- data/src/rules/node_ext.rs +1 -1
- data/src/rules/ruby_literal.rs +8 -3
- data/src/rules/security/compound_hash.rs +3 -2
- data/src/rules/security/open.rs +5 -4
- data/src/rules/send_node.rs +134 -0
- data/src/rules/single_line.rs +4 -4
- data/src/rules/style/access_modifier_declarations.rs +76 -22
- data/src/rules/style/accessor_grouping.rs +44 -6
- data/src/rules/style/alias.rs +4 -3
- data/src/rules/style/and_or.rs +2 -2
- data/src/rules/style/arguments_forwarding.rs +5 -5
- data/src/rules/style/array_coercion.rs +5 -5
- data/src/rules/style/array_first_last.rs +19 -4
- data/src/rules/style/array_intersect.rs +26 -8
- data/src/rules/style/array_intersect_with_single_element.rs +1 -1
- data/src/rules/style/ascii_comments.rs +2 -0
- data/src/rules/style/attr.rs +1 -1
- data/src/rules/style/auto_resource_cleanup.rs +1 -1
- data/src/rules/style/bitwise_predicate.rs +2 -2
- data/src/rules/style/block_args.rs +1 -1
- data/src/rules/style/block_comments.rs +11 -2
- data/src/rules/style/block_delimiters.rs +154 -10
- data/src/rules/style/case_equality.rs +7 -2
- data/src/rules/style/case_like_if.rs +26 -2
- data/src/rules/style/class_and_module_children.rs +162 -10
- data/src/rules/style/class_methods.rs +1 -1
- data/src/rules/style/class_methods_definitions.rs +9 -0
- data/src/rules/style/collection_compact.rs +4 -4
- data/src/rules/style/combinable_defined.rs +2 -2
- data/src/rules/style/combinable_loops.rs +9 -11
- data/src/rules/style/comments.rs +6 -3
- data/src/rules/style/comparable_clamp.rs +2 -2
- data/src/rules/style/concat_array_literals.rs +3 -3
- data/src/rules/style/conditional.rs +12 -1
- data/src/rules/style/conditional_assignment.rs +133 -4
- data/src/rules/style/constant_visibility.rs +3 -3
- data/src/rules/style/copyright.rs +64 -3
- data/src/rules/style/data_inheritance.rs +1 -1
- data/src/rules/style/def_with_parentheses.rs +1 -1
- data/src/rules/style/document_dynamic_eval_definition.rs +50 -12
- data/src/rules/style/documentation.rs +1 -1
- data/src/rules/style/each_for_simple_loop.rs +1 -1
- data/src/rules/style/each_with_object.rs +4 -4
- data/src/rules/style/empty_block_parameter.rs +1 -1
- data/src/rules/style/empty_case_condition.rs +6 -6
- data/src/rules/style/empty_class_definition.rs +1 -1
- data/src/rules/style/empty_else.rs +1 -1
- data/src/rules/style/empty_heredoc.rs +14 -1
- data/src/rules/style/empty_lambda_parameter.rs +1 -1
- data/src/rules/style/empty_literal.rs +34 -2
- data/src/rules/style/empty_method.rs +1 -1
- data/src/rules/style/empty_string_inside_interpolation.rs +1 -1
- data/src/rules/style/endless.rs +11 -3
- data/src/rules/style/env_home.rs +1 -1
- data/src/rules/style/eval_with_location.rs +6 -0
- data/src/rules/style/even_odd.rs +1 -1
- data/src/rules/style/exact_regexp_match.rs +1 -1
- data/src/rules/style/expand_path_arguments.rs +3 -3
- data/src/rules/style/explicit_block_argument.rs +3 -3
- data/src/rules/style/file_open.rs +5 -0
- data/src/rules/style/file_open_call.rs +3 -3
- data/src/rules/style/file_write.rs +1 -1
- data/src/rules/style/for.rs +1 -1
- data/src/rules/style/format_sequences.rs +6 -0
- data/src/rules/style/format_string.rs +75 -11
- data/src/rules/style/format_string_token.rs +61 -12
- data/src/rules/style/frozen_string_literal_comment.rs +62 -4
- data/src/rules/style/guard_clause.rs +8 -7
- data/src/rules/style/hash_as_last_array_item.rs +4 -4
- data/src/rules/style/hash_each_methods.rs +1 -1
- data/src/rules/style/hash_like_case.rs +8 -8
- data/src/rules/style/hash_lookup_method.rs +43 -3
- data/src/rules/style/hash_subset.rs +16 -10
- data/src/rules/style/hash_syntax.rs +609 -71
- data/src/rules/style/hash_transform.rs +20 -18
- data/src/rules/style/hash_transform_keys.rs +7 -0
- data/src/rules/style/hash_transform_values.rs +7 -0
- data/src/rules/style/identical_conditional_branches.rs +54 -5
- data/src/rules/style/if_inside_else.rs +19 -3
- data/src/rules/style/if_unless_modifier.rs +13 -14
- data/src/rules/style/if_with_boolean_literal_branches.rs +1 -1
- data/src/rules/style/if_with_semicolon.rs +7 -1
- data/src/rules/style/implicit_runtime_error.rs +7 -0
- data/src/rules/style/in_pattern_then.rs +2 -2
- data/src/rules/style/infinite_loop.rs +6 -0
- data/src/rules/style/inline_comment.rs +4 -1
- data/src/rules/style/inverse_methods.rs +12 -2
- data/src/rules/style/invertible_unless_condition.rs +12 -12
- data/src/rules/style/it_block_parameter.rs +5 -2
- data/src/rules/style/keyword_arguments_merging.rs +8 -2
- data/src/rules/style/keyword_parameters_order.rs +1 -1
- data/src/rules/style/line_end_concatenation.rs +1 -1
- data/src/rules/style/magic_comment_format.rs +14 -6
- data/src/rules/style/map_chain.rs +1 -1
- data/src/rules/style/map_compact_with_conditional_block.rs +3 -3
- data/src/rules/style/map_into_array.rs +7 -7
- data/src/rules/style/map_join.rs +3 -3
- data/src/rules/style/method_call_with_args_parentheses.rs +7 -7
- data/src/rules/style/method_call_without_args_parentheses.rs +2 -2
- data/src/rules/style/method_def_parentheses.rs +2 -3
- data/src/rules/style/min_max.rs +3 -3
- data/src/rules/style/min_max_comparison.rs +2 -2
- data/src/rules/style/missing_else.rs +1 -1
- data/src/rules/style/missing_respond_to_missing.rs +1 -1
- data/src/rules/style/mixin_grouping.rs +6 -7
- data/src/rules/style/multiline_block_chain.rs +6 -8
- data/src/rules/style/multiline_if_then.rs +1 -1
- data/src/rules/style/multiline_in_pattern_then.rs +16 -3
- data/src/rules/style/multiline_method_signature.rs +1 -1
- data/src/rules/style/multiline_when_then.rs +2 -2
- data/src/rules/style/mutable_constant.rs +31 -35
- data/src/rules/style/negated_if.rs +2 -2
- data/src/rules/style/negated_if_else_condition.rs +5 -3
- data/src/rules/style/negated_while.rs +1 -1
- data/src/rules/style/negative_array_index.rs +25 -3
- data/src/rules/style/nested_modifier.rs +1 -1
- data/src/rules/style/nested_parenthesized_calls.rs +2 -2
- data/src/rules/style/nested_ternary_operator.rs +2 -2
- data/src/rules/style/next.rs +12 -12
- data/src/rules/style/nil_lambda.rs +2 -2
- data/src/rules/style/nodes.rs +21 -0
- data/src/rules/style/non_nil_check.rs +82 -2
- data/src/rules/style/numbered_parameters.rs +4 -1
- data/src/rules/style/numbered_parameters_limit.rs +2 -2
- data/src/rules/style/numeric_literals.rs +17 -1
- data/src/rules/style/numeric_predicate.rs +25 -6
- data/src/rules/style/one_line_conditional.rs +24 -7
- data/src/rules/style/operator_method_call.rs +5 -0
- data/src/rules/style/optional_arguments.rs +1 -1
- data/src/rules/style/or_assignment.rs +1 -1
- data/src/rules/style/parallel_assignment.rs +20 -9
- data/src/rules/style/parens.rs +1 -1
- data/src/rules/style/parentheses_around_condition.rs +3 -3
- data/src/rules/style/partition_instead_of_double_select.rs +3 -3
- data/src/rules/style/percent.rs +18 -9
- data/src/rules/style/percent_q_literals.rs +40 -7
- data/src/rules/style/preferred_hash_methods.rs +1 -1
- data/src/rules/style/quoted_symbols.rs +6 -0
- data/src/rules/style/raise_args.rs +26 -20
- data/src/rules/style/random_with_offset.rs +1 -1
- data/src/rules/style/reduce_to_hash.rs +6 -6
- data/src/rules/style/redundant_argument.rs +1 -1
- data/src/rules/style/redundant_assignment.rs +5 -5
- data/src/rules/style/redundant_begin.rs +5 -3
- data/src/rules/style/redundant_capital_w.rs +3 -2
- data/src/rules/style/redundant_condition.rs +17 -8
- data/src/rules/style/redundant_current_directory_in_path.rs +2 -2
- data/src/rules/style/redundant_double_splat_hash_braces.rs +15 -5
- data/src/rules/style/redundant_exception.rs +3 -1
- data/src/rules/style/redundant_file_extension_in_require.rs +2 -2
- data/src/rules/style/redundant_format.rs +98 -20
- data/src/rules/style/redundant_freeze.rs +1 -1
- data/src/rules/style/redundant_heredoc_delimiter_quotes.rs +1 -1
- data/src/rules/style/redundant_initialize.rs +6 -3
- data/src/rules/style/redundant_interpolation.rs +8 -8
- data/src/rules/style/redundant_interpolation_unfreeze.rs +10 -3
- data/src/rules/style/redundant_line_continuation.rs +10 -7
- data/src/rules/style/redundant_parentheses.rs +96 -19
- data/src/rules/style/redundant_regexp_argument.rs +1 -1
- data/src/rules/style/redundant_regexp_character_class.rs +4 -3
- data/src/rules/style/redundant_regexp_escape.rs +3 -2
- data/src/rules/style/redundant_return.rs +1 -2
- data/src/rules/style/redundant_self.rs +28 -16
- data/src/rules/style/redundant_sort.rs +1 -1
- data/src/rules/style/redundant_sort_by.rs +1 -1
- data/src/rules/style/redundant_string_escape.rs +6 -5
- data/src/rules/style/regexp_literal.rs +3 -2
- data/src/rules/style/require_order.rs +2 -2
- data/src/rules/style/rescue_modifier.rs +9 -4
- data/src/rules/style/rescue_standard_error.rs +1 -1
- data/src/rules/style/return_nil.rs +2 -2
- data/src/rules/style/return_nil_in_predicate_method_definition.rs +2 -2
- data/src/rules/style/safe_navigation.rs +2 -2
- data/src/rules/style/safe_navigation_chain_length.rs +8 -1
- data/src/rules/style/sample.rs +3 -3
- data/src/rules/style/select_by.rs +6 -2
- data/src/rules/style/select_by_range.rs +1 -1
- data/src/rules/style/semicolon.rs +77 -4
- data/src/rules/style/signal_exception.rs +60 -3
- data/src/rules/style/single_argument_dig.rs +10 -2
- data/src/rules/style/single_line_block_params.rs +14 -4
- data/src/rules/style/single_line_do_end_block.rs +13 -12
- data/src/rules/style/single_line_methods.rs +2 -2
- data/src/rules/style/slicing_with_range.rs +2 -2
- data/src/rules/style/sole_nested_conditional.rs +4 -4
- data/src/rules/style/special_global_vars.rs +2 -2
- data/src/rules/style/stabby_lambda_parentheses.rs +1 -1
- data/src/rules/style/statement_modifier.rs +3 -2
- data/src/rules/style/static_class.rs +7 -1
- data/src/rules/style/string_concatenation.rs +12 -7
- data/src/rules/style/string_hash_keys.rs +3 -2
- data/src/rules/style/string_literals.rs +3 -3
- data/src/rules/style/string_literals_in_interpolation.rs +4 -1
- data/src/rules/style/struct_inheritance.rs +3 -3
- data/src/rules/style/super_arguments.rs +15 -7
- data/src/rules/style/super_with_args_parentheses.rs +49 -12
- data/src/rules/style/swap_values.rs +1 -1
- data/src/rules/style/symbol_array.rs +9 -2
- data/src/rules/style/symbol_proc.rs +3 -3
- data/src/rules/style/tally_method.rs +4 -4
- data/src/rules/style/ternary_parentheses.rs +30 -15
- data/src/rules/style/top_level_method_definition.rs +10 -5
- data/src/rules/style/trailing_body.rs +1 -1
- data/src/rules/style/trailing_comma.rs +206 -10
- data/src/rules/style/trailing_comma_in_arguments.rs +9 -10
- data/src/rules/style/trailing_comma_in_array_literal.rs +2 -1
- data/src/rules/style/trailing_comma_in_hash_literal.rs +2 -1
- data/src/rules/style/trailing_method_end_statement.rs +6 -1
- data/src/rules/style/trailing_underscore_variable.rs +4 -5
- data/src/rules/style/trivial_accessors.rs +2 -2
- data/src/rules/style/unless_logical_operators.rs +1 -1
- data/src/rules/style/unpack_first.rs +11 -2
- data/src/rules/style/when_then.rs +4 -4
- data/src/rules/style/while_until_do.rs +1 -1
- data/src/rules/style/while_until_modifier.rs +3 -3
- data/src/rules/style/word_array.rs +9 -7
- data/src/rules/style/yoda_condition.rs +2 -2
- data/src/rules/style/zero_length_predicate.rs +5 -0
- data/src/rules/support.rs +128 -11
- data/src/source.rs +18 -15
- metadata +5 -1
|
@@ -136,17 +136,31 @@ pub(in crate::rules) struct Scope<'tree> {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
pub(in crate::rules) struct Analysis<'tree> {
|
|
139
|
+
/// The file's node index, which answers a parent with one hash lookup. `Node::parent` walks
|
|
140
|
+
/// down from the root, and the force asks for a parent on nearly every node it visits.
|
|
141
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
139
142
|
/// Every scope, in the order they were left, which is the order the cops report in.
|
|
140
143
|
pub scopes: Vec<Scope<'tree>>,
|
|
141
144
|
pub variables: Vec<Variable<'tree>>,
|
|
142
145
|
/// The identifiers that resolved to a local variable. tree-sitter cannot tell a read of a
|
|
143
146
|
/// local from a receiverless call, and only the analysis knows which one the parser upstream
|
|
144
147
|
/// would have built.
|
|
145
|
-
lvars:
|
|
148
|
+
lvars: crate::rules::IdSet,
|
|
149
|
+
/// The references Naming handlers see. This includes implicit numbered block parameters,
|
|
150
|
+
/// which are local-variable reads but are not declarations VariableForce reports.
|
|
151
|
+
naming_references: crate::rules::IdSet,
|
|
146
152
|
/// The `foo()` calls whose name a local variable already holds. The parentheses make these
|
|
147
153
|
/// calls whatever the name resolves to, so nothing here is an `lvar` -- but writing the same
|
|
148
154
|
/// name without them would be one, which is the question a cop that drops parentheses asks.
|
|
149
|
-
shadowed_calls:
|
|
155
|
+
shadowed_calls: crate::rules::IdSet,
|
|
156
|
+
/// Receiverless call-name nodes whose spelling is already held by a local variable. Unlike
|
|
157
|
+
/// `shadowed_calls`, this also records calls with arguments for syntax recovery such as
|
|
158
|
+
/// `collection [0]`.
|
|
159
|
+
local_method_names: crate::rules::IdSet,
|
|
160
|
+
/// The identifier nodes that RuboCop dispatches as variable definitions. Pattern bindings and
|
|
161
|
+
/// explicit block-local variables deliberately stay out: upstream names them `match_var` and
|
|
162
|
+
/// `shadowarg`, neither of which the Naming cops handle.
|
|
163
|
+
naming_definitions: crate::rules::IdSet,
|
|
150
164
|
}
|
|
151
165
|
|
|
152
166
|
impl<'tree> Analysis<'tree> {
|
|
@@ -161,27 +175,78 @@ impl<'tree> Analysis<'tree> {
|
|
|
161
175
|
self.shadowed_calls.contains(&node.id())
|
|
162
176
|
}
|
|
163
177
|
|
|
164
|
-
pub(in crate::rules) fn
|
|
178
|
+
pub(in crate::rules) fn names_a_local(&self, node: Node<'_>) -> bool {
|
|
179
|
+
self.local_method_names.contains(&node.id())
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Whether the Naming cops see this node as a variable read or definition.
|
|
183
|
+
pub(in crate::rules) fn is_naming_variable(&self, node: Node<'_>) -> bool {
|
|
184
|
+
self.is_naming_definition(node) || self.naming_references.contains(&node.id())
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
pub(in crate::rules) fn is_variable(&self, node: Node<'_>) -> bool {
|
|
188
|
+
self.is_naming_variable(node)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pub(in crate::rules) fn is_reference(&self, node: Node<'_>) -> bool {
|
|
192
|
+
self.naming_references.contains(&node.id())
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Whether this node is the target of the `lvasgn` shape upstream exposes. Parameter
|
|
196
|
+
/// declarations are naming definitions too, but an AST walk restricted to `lvasgn` does not
|
|
197
|
+
/// visit them.
|
|
198
|
+
pub(in crate::rules) fn is_local_assignment(&self, node: Node<'_>) -> bool {
|
|
199
|
+
self.is_naming_definition(node) && structural_definition(node, self.index)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Whether the parser would dispatch this node to a Naming variable-definition handler.
|
|
203
|
+
pub(in crate::rules) fn is_naming_definition(&self, node: Node<'_>) -> bool {
|
|
204
|
+
if node.kind_str() == "identifier" {
|
|
205
|
+
return self.naming_definitions.contains(&node.id());
|
|
206
|
+
}
|
|
207
|
+
matches!(
|
|
208
|
+
node.kind_str(),
|
|
209
|
+
"instance_variable" | "class_variable" | "global_variable"
|
|
210
|
+
) && structural_definition(node, self.index)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
pub(in crate::rules) fn is_definition(&self, node: Node<'_>) -> bool {
|
|
214
|
+
self.is_naming_definition(node)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
pub(in crate::rules) fn run(
|
|
218
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
219
|
+
source: &SourceFile,
|
|
220
|
+
) -> Self {
|
|
221
|
+
let root = index.root_node();
|
|
165
222
|
let mut force = Force {
|
|
223
|
+
index,
|
|
166
224
|
source,
|
|
167
225
|
scopes: Vec::new(),
|
|
168
226
|
variables: Vec::new(),
|
|
169
227
|
stack: Vec::new(),
|
|
170
228
|
branches: Vec::new(),
|
|
171
229
|
branch_index: HashMap::new(),
|
|
172
|
-
heredocs: heredoc_bodies(
|
|
173
|
-
scanned:
|
|
174
|
-
lvars:
|
|
175
|
-
|
|
230
|
+
heredocs: heredoc_bodies(index),
|
|
231
|
+
scanned: crate::rules::IdSet::default(),
|
|
232
|
+
lvars: crate::rules::IdSet::default(),
|
|
233
|
+
naming_references: crate::rules::IdSet::default(),
|
|
234
|
+
shadowed_calls: crate::rules::IdSet::default(),
|
|
235
|
+
local_method_names: crate::rules::IdSet::default(),
|
|
236
|
+
naming_definitions: crate::rules::IdSet::default(),
|
|
176
237
|
};
|
|
177
238
|
force.push_scope(root, true);
|
|
178
239
|
force.process_children(root);
|
|
179
240
|
force.pop_scope();
|
|
180
241
|
Analysis {
|
|
242
|
+
index,
|
|
181
243
|
scopes: force.scopes,
|
|
182
244
|
variables: force.variables,
|
|
183
245
|
lvars: force.lvars,
|
|
246
|
+
naming_references: force.naming_references,
|
|
184
247
|
shadowed_calls: force.shadowed_calls,
|
|
248
|
+
local_method_names: force.local_method_names,
|
|
249
|
+
naming_definitions: force.naming_definitions,
|
|
185
250
|
}
|
|
186
251
|
}
|
|
187
252
|
}
|
|
@@ -207,6 +272,7 @@ struct Frame<'tree> {
|
|
|
207
272
|
}
|
|
208
273
|
|
|
209
274
|
struct Force<'tree, 'a> {
|
|
275
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
210
276
|
source: &'a SourceFile,
|
|
211
277
|
scopes: Vec<Scope<'tree>>,
|
|
212
278
|
variables: Vec<Variable<'tree>>,
|
|
@@ -216,11 +282,14 @@ struct Force<'tree, 'a> {
|
|
|
216
282
|
/// Each heredoc's body, found from the `<<~X` that opened it. tree-sitter hangs the body off
|
|
217
283
|
/// the enclosing statement, so a heredoc written inside a block would otherwise have its
|
|
218
284
|
/// interpolations resolved in the wrong scope.
|
|
219
|
-
heredocs:
|
|
285
|
+
heredocs: crate::rules::IdKeyed<Node<'tree>>,
|
|
220
286
|
/// Nodes already walked in an outer scope, which the scope they sit in must not walk again.
|
|
221
|
-
scanned:
|
|
222
|
-
lvars:
|
|
223
|
-
|
|
287
|
+
scanned: crate::rules::IdSet,
|
|
288
|
+
lvars: crate::rules::IdSet,
|
|
289
|
+
naming_references: crate::rules::IdSet,
|
|
290
|
+
shadowed_calls: crate::rules::IdSet,
|
|
291
|
+
local_method_names: crate::rules::IdSet,
|
|
292
|
+
naming_definitions: crate::rules::IdSet,
|
|
224
293
|
}
|
|
225
294
|
|
|
226
295
|
// ---------------------------------------------------------------------------
|
|
@@ -242,6 +311,34 @@ fn mislexed_match_operator(node: Node<'_>, source: &SourceFile) -> bool {
|
|
|
242
311
|
operator.end_byte() == right.start_byte() && source.node_text(right).starts_with('~')
|
|
243
312
|
}
|
|
244
313
|
|
|
314
|
+
/// Whether a non-local variable node is written in a position RuboCop dispatches as a variable
|
|
315
|
+
/// definition. Local identifiers are recorded during the force walk, but instance, class and
|
|
316
|
+
/// global variables do not participate in local-variable resolution and are cheaper to classify
|
|
317
|
+
/// directly from their parent.
|
|
318
|
+
fn structural_definition(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
|
|
319
|
+
let Some(parent) = index.parent(node) else {
|
|
320
|
+
return false;
|
|
321
|
+
};
|
|
322
|
+
match parent.kind_str() {
|
|
323
|
+
"assignment" | "operator_assignment" => parent
|
|
324
|
+
.field("left")
|
|
325
|
+
.is_some_and(|left| left.id() == node.id()),
|
|
326
|
+
"left_assignment_list" if spurious_assignment_list(parent) => {
|
|
327
|
+
let mut cursor = parent.walk();
|
|
328
|
+
parent
|
|
329
|
+
.named_children(&mut cursor)
|
|
330
|
+
.last()
|
|
331
|
+
.is_some_and(|last| last.id() == node.id())
|
|
332
|
+
}
|
|
333
|
+
"left_assignment_list" | "destructured_left_assignment" | "rest_assignment" => true,
|
|
334
|
+
"for" => parent
|
|
335
|
+
.field("pattern")
|
|
336
|
+
.is_some_and(|pattern| pattern.id() == node.id()),
|
|
337
|
+
"exception_variable" => true,
|
|
338
|
+
_ => false,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
245
342
|
/// What an assignment really stores. When the grammar swallowed the neighbouring items of a
|
|
246
343
|
/// comma-separated list, the node it made the right-hand side spans all of them, and only its
|
|
247
344
|
/// first element belongs to this write.
|
|
@@ -275,12 +372,29 @@ pub(super) fn scope_nodes<'tree>(scope: &Scope<'tree>) -> Vec<Node<'tree>> {
|
|
|
275
372
|
}
|
|
276
373
|
|
|
277
374
|
fn scan_scope<'tree>(node: Node<'tree>, scope_node: Node<'tree>, nodes: &mut Vec<Node<'tree>>) {
|
|
278
|
-
|
|
375
|
+
let mut cursor = node.walk();
|
|
376
|
+
if !cursor.goto_first_child() {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
loop {
|
|
380
|
+
let child = cursor.node();
|
|
381
|
+
if !child.is_named() {
|
|
382
|
+
if !cursor.goto_next_sibling() {
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
279
387
|
if !owned_by_scope(child, node, scope_node) {
|
|
388
|
+
if !cursor.goto_next_sibling() {
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
280
391
|
continue;
|
|
281
392
|
}
|
|
282
393
|
nodes.push(child);
|
|
283
394
|
scan_scope(child, scope_node, nodes);
|
|
395
|
+
if !cursor.goto_next_sibling() {
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
284
398
|
}
|
|
285
399
|
}
|
|
286
400
|
|
|
@@ -317,20 +431,33 @@ pub(super) fn body_node<'tree>(scope: &Scope<'tree>) -> Option<Node<'tree>> {
|
|
|
317
431
|
}
|
|
318
432
|
|
|
319
433
|
/// The call a block was written on, which names the method the block was passed to.
|
|
320
|
-
pub(super) fn block_call<'tree>(
|
|
434
|
+
pub(super) fn block_call<'tree>(
|
|
435
|
+
scope_node: Node<'tree>,
|
|
436
|
+
index: &super::super::AstIndex<'tree>,
|
|
437
|
+
) -> Option<Node<'tree>> {
|
|
321
438
|
match scope_node.kind_str() {
|
|
322
|
-
"block" | "do_block" =>
|
|
439
|
+
"block" | "do_block" => index
|
|
440
|
+
.parent_in_tree(scope_node)
|
|
441
|
+
.filter(|node| node.kind_str() == "call"),
|
|
323
442
|
_ => None,
|
|
324
443
|
}
|
|
325
444
|
}
|
|
326
445
|
|
|
327
446
|
/// `BlockNode#lambda?`: both `->() {}` and `lambda {}` reach RuboCop as a block on `lambda`.
|
|
328
|
-
pub(super) fn is_lambda(
|
|
329
|
-
scope_node
|
|
447
|
+
pub(super) fn is_lambda(
|
|
448
|
+
scope_node: Node<'_>,
|
|
449
|
+
source: &SourceFile,
|
|
450
|
+
index: &super::super::AstIndex<'_>,
|
|
451
|
+
) -> bool {
|
|
452
|
+
scope_node.kind_str() == "lambda" || block_method(scope_node, source, index) == Some("lambda")
|
|
330
453
|
}
|
|
331
454
|
|
|
332
|
-
pub(super) fn block_method<'a>(
|
|
333
|
-
|
|
455
|
+
pub(super) fn block_method<'a>(
|
|
456
|
+
scope_node: Node<'_>,
|
|
457
|
+
source: &'a SourceFile,
|
|
458
|
+
index: &super::super::AstIndex<'_>,
|
|
459
|
+
) -> Option<&'a str> {
|
|
460
|
+
let call = block_call(scope_node, index)?;
|
|
334
461
|
Some(source.node_text(call.field("method")?))
|
|
335
462
|
}
|
|
336
463
|
|
|
@@ -367,8 +494,15 @@ impl<'tree> Force<'tree, '_> {
|
|
|
367
494
|
});
|
|
368
495
|
}
|
|
369
496
|
|
|
497
|
+
/// A node's named children, taken from the file's index. The list borrows the index rather
|
|
498
|
+
/// than this walk, so a `&mut self` call inside the loop is still allowed.
|
|
499
|
+
fn children(&self, node: Node<'tree>) -> std::borrow::Cow<'tree, [Node<'tree>]> {
|
|
500
|
+
super::super::send_node::named_children_in(node, self.index)
|
|
501
|
+
}
|
|
502
|
+
|
|
370
503
|
fn process_children(&mut self, node: Node<'tree>) {
|
|
371
|
-
|
|
504
|
+
let children = self.children(node);
|
|
505
|
+
for child in children.iter().copied() {
|
|
372
506
|
if !self.scanned.contains(&child.id()) {
|
|
373
507
|
self.process_node(child);
|
|
374
508
|
}
|
|
@@ -391,6 +525,14 @@ impl<'tree> Force<'tree, '_> {
|
|
|
391
525
|
}
|
|
392
526
|
// `foo = 1 if bar` runs its condition first, but tree-sitter writes the body first.
|
|
393
527
|
"if_modifier" | "unless_modifier" => {
|
|
528
|
+
// The parser registers a local the moment it *reads* the assignment, so the `paths`
|
|
529
|
+
// in `paths = [paths] unless paths.is_a?(Array)` is an `lvar` on both sides of the
|
|
530
|
+
// keyword -- which is the whole of what `Style/ArrayCoercion` matches on. Declaring
|
|
531
|
+
// the names the body writes, with no assignment behind them yet, gets that spelling
|
|
532
|
+
// right while the walk below still reaches them in execution order.
|
|
533
|
+
if let Some(body) = node.field("body") {
|
|
534
|
+
self.declare_lexically(body);
|
|
535
|
+
}
|
|
394
536
|
if let Some(condition) = node.field("condition") {
|
|
395
537
|
self.process_node(condition);
|
|
396
538
|
}
|
|
@@ -430,8 +572,9 @@ impl<'tree> Force<'tree, '_> {
|
|
|
430
572
|
/// block node holding both the parameters and the body, so the inner node is not a scope.
|
|
431
573
|
fn inline_block(&self, node: Node<'tree>) -> bool {
|
|
432
574
|
node.kind_str() == "block"
|
|
433
|
-
&&
|
|
434
|
-
.
|
|
575
|
+
&& self
|
|
576
|
+
.index
|
|
577
|
+
.parent(node)
|
|
435
578
|
.is_some_and(|parent| parent.kind_str() == "lambda")
|
|
436
579
|
}
|
|
437
580
|
|
|
@@ -469,6 +612,9 @@ impl<'tree> Force<'tree, '_> {
|
|
|
469
612
|
name_node: Node<'tree>,
|
|
470
613
|
kind: Declaration,
|
|
471
614
|
) {
|
|
615
|
+
if matches!(kind, Declaration::Argument(_)) {
|
|
616
|
+
self.naming_definitions.insert(name_node.id());
|
|
617
|
+
}
|
|
472
618
|
let index = self.variables.len();
|
|
473
619
|
let frame = self.stack.last_mut().expect("scope stack is never empty");
|
|
474
620
|
self.variables.push(Variable {
|
|
@@ -496,6 +642,31 @@ impl<'tree> Force<'tree, '_> {
|
|
|
496
642
|
}
|
|
497
643
|
}
|
|
498
644
|
|
|
645
|
+
/// Introduces the names an assignment inside `node` would introduce, without recording the
|
|
646
|
+
/// assignment itself -- the lexical half of what the parser does when it reaches one.
|
|
647
|
+
///
|
|
648
|
+
/// Anything opening a scope of its own is left alone: a name written inside a block or a `def`
|
|
649
|
+
/// does not survive it, so the parser does not spell it as a variable outside either.
|
|
650
|
+
fn declare_lexically(&mut self, node: Node<'tree>) {
|
|
651
|
+
if opens_a_scope(node) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Only the plain `foo = ...` target is introduced here. A multiple assignment reaches the
|
|
655
|
+
// same names through `process_assignment`, whose handling of `left_assignment_list` is not
|
|
656
|
+
// worth duplicating for a shape the spelling question has never turned on.
|
|
657
|
+
if matches!(node.kind_str(), "assignment" | "operator_assignment")
|
|
658
|
+
&& let Some(left) = node.field("left")
|
|
659
|
+
&& left.kind_str() == "identifier"
|
|
660
|
+
{
|
|
661
|
+
let name = self.text(left).to_owned();
|
|
662
|
+
self.declare_unless_known(&name, node, left);
|
|
663
|
+
}
|
|
664
|
+
let mut cursor = node.walk();
|
|
665
|
+
for child in node.children(&mut cursor).collect::<Vec<_>>() {
|
|
666
|
+
self.declare_lexically(child);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
499
670
|
/// Whether the scope now being walked is a block other than the one the variable belongs to,
|
|
500
671
|
/// which is what makes the variable outlive the reads this walk can see.
|
|
501
672
|
fn capture_if_needed(&mut self, variable: usize) {
|
|
@@ -515,6 +686,9 @@ impl<'tree> Force<'tree, '_> {
|
|
|
515
686
|
value: Option<Node<'tree>>,
|
|
516
687
|
kind: AssignmentKind,
|
|
517
688
|
) {
|
|
689
|
+
if kind == AssignmentKind::Plain {
|
|
690
|
+
self.naming_definitions.insert(name.id());
|
|
691
|
+
}
|
|
518
692
|
self.capture_if_needed(variable);
|
|
519
693
|
let branch = self.branch_of(node);
|
|
520
694
|
let captured = self.variables[variable].captured_by_block;
|
|
@@ -573,7 +747,7 @@ impl<'tree> Force<'tree, '_> {
|
|
|
573
747
|
.push(node);
|
|
574
748
|
}
|
|
575
749
|
let assignment_node = self.variables[variable].assignments[index].node;
|
|
576
|
-
if in_modifier_conditional(assignment_node, node) {
|
|
750
|
+
if in_modifier_conditional(assignment_node, node, self.index) {
|
|
577
751
|
continue;
|
|
578
752
|
}
|
|
579
753
|
let Some(branch) = branch else { break };
|
|
@@ -728,23 +902,35 @@ impl<'tree> Force<'tree, '_> {
|
|
|
728
902
|
// Only tree-sitter spells a method name with the same node type as a variable read; the
|
|
729
903
|
// parser upstream turns `def foo` and `alias foo bar` into symbols. The receiver of
|
|
730
904
|
// `def obj.foo` is a genuine read and stays.
|
|
731
|
-
if
|
|
905
|
+
if self.index.parent(node).is_some_and(|parent| {
|
|
732
906
|
matches!(parent.kind_str(), "alias" | "undef" | "setter")
|
|
733
907
|
|| (matches!(parent.kind_str(), "method" | "singleton_method")
|
|
734
|
-
&& field_name(node, parent) == Some("name"))
|
|
908
|
+
&& field_name(node, parent, self.index) == Some("name"))
|
|
735
909
|
}) {
|
|
736
910
|
return;
|
|
737
911
|
}
|
|
738
912
|
let name = self.text(node);
|
|
739
913
|
if let Some(variable) = self.find_variable(name) {
|
|
740
914
|
self.lvars.insert(node.id());
|
|
915
|
+
self.naming_references.insert(node.id());
|
|
741
916
|
self.reference(variable, node);
|
|
917
|
+
} else if implicit_numbered_parameter(name)
|
|
918
|
+
&& self.stack.last().is_some_and(|frame| frame.block)
|
|
919
|
+
{
|
|
920
|
+
self.naming_references.insert(node.id());
|
|
742
921
|
} else if name == "binding" {
|
|
743
922
|
self.reference_everything(node);
|
|
744
923
|
}
|
|
745
924
|
}
|
|
746
925
|
|
|
747
926
|
fn process_call(&mut self, node: Node<'tree>) {
|
|
927
|
+
if node.field("receiver").is_none()
|
|
928
|
+
&& let Some(method) = node.field("method")
|
|
929
|
+
&& method.kind_str() == "identifier"
|
|
930
|
+
&& self.find_variable(self.text(method)).is_some()
|
|
931
|
+
{
|
|
932
|
+
self.local_method_names.insert(method.id());
|
|
933
|
+
}
|
|
748
934
|
if self.binary_operator_on_a_local(node) {
|
|
749
935
|
return;
|
|
750
936
|
}
|
|
@@ -1168,7 +1354,7 @@ impl<'tree> Force<'tree, '_> {
|
|
|
1168
1354
|
fn mark_assignments_referenced_in_loop(&mut self, node: Node<'tree>) {
|
|
1169
1355
|
let mut names = Vec::new();
|
|
1170
1356
|
let mut assignments = HashSet::new();
|
|
1171
|
-
collect_loop_references(node, self.source, &mut names, &mut assignments);
|
|
1357
|
+
collect_loop_references(node, self.source, self.index, &mut names, &mut assignments);
|
|
1172
1358
|
for name in names {
|
|
1173
1359
|
let Some(variable) = self.find_variable(&name) else {
|
|
1174
1360
|
continue;
|
|
@@ -1187,7 +1373,7 @@ impl<'tree> Force<'tree, '_> {
|
|
|
1187
1373
|
};
|
|
1188
1374
|
for &index in &indices {
|
|
1189
1375
|
let assignment = &self.variables[variable].assignments[index];
|
|
1190
|
-
if has_branch_ancestor(assignment.node) {
|
|
1376
|
+
if has_branch_ancestor(assignment.node, self.index) {
|
|
1191
1377
|
self.variables[variable].assignments[index].referenced = true;
|
|
1192
1378
|
}
|
|
1193
1379
|
}
|
|
@@ -1216,8 +1402,8 @@ impl<'tree> Force<'tree, '_> {
|
|
|
1216
1402
|
if !top_level && current.id() == scope_node.id() {
|
|
1217
1403
|
return None;
|
|
1218
1404
|
}
|
|
1219
|
-
let parent =
|
|
1220
|
-
if let Some(role) = branch_role(current, parent)
|
|
1405
|
+
let parent = self.index.parent_in_tree(current)?;
|
|
1406
|
+
if let Some(role) = branch_role(current, parent, self.index)
|
|
1221
1407
|
&& role.branched
|
|
1222
1408
|
{
|
|
1223
1409
|
return Some(self.intern_branch(role, parent, scope_node, top_level));
|
|
@@ -1278,14 +1464,22 @@ impl<'tree> Force<'tree, '_> {
|
|
|
1278
1464
|
}
|
|
1279
1465
|
}
|
|
1280
1466
|
|
|
1467
|
+
fn implicit_numbered_parameter(name: &str) -> bool {
|
|
1468
|
+
matches!(name.as_bytes(), [b'_', b'1'..=b'9'])
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1281
1471
|
// ---------------------------------------------------------------------------
|
|
1282
1472
|
// Node helpers
|
|
1283
1473
|
// ---------------------------------------------------------------------------
|
|
1284
1474
|
|
|
1285
1475
|
/// Whether the child stands in a branch of the control structure above it, and how that branch
|
|
1286
1476
|
/// behaves: `(may_run_incompletely, may_jump_to_other_branch, branched)`.
|
|
1287
|
-
fn branch_role<'tree>(
|
|
1288
|
-
|
|
1477
|
+
fn branch_role<'tree>(
|
|
1478
|
+
child: Node<'tree>,
|
|
1479
|
+
parent: Node<'tree>,
|
|
1480
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
1481
|
+
) -> Option<BranchRole<'tree>> {
|
|
1482
|
+
let field = field_name(child, parent, index);
|
|
1289
1483
|
let always_run = BranchRole::always_run(child);
|
|
1290
1484
|
let branched = BranchRole::branched(child);
|
|
1291
1485
|
match parent.kind_str() {
|
|
@@ -1316,10 +1510,10 @@ fn branch_role<'tree>(child: Node<'tree>, parent: Node<'tree>) -> Option<BranchR
|
|
|
1316
1510
|
// `begin … rescue … ensure … end` has no node of its own here: the clauses stand beside
|
|
1317
1511
|
// the statements they guard. Upstream wraps those statements in one node, and the whole
|
|
1318
1512
|
// main body is a single branch, so they all have to point at the same child.
|
|
1319
|
-
_ if is_rescue_container(parent) => Some(match child.kind_str() {
|
|
1513
|
+
_ if is_rescue_container(parent, index) => Some(match child.kind_str() {
|
|
1320
1514
|
"rescue" | "else" => branched,
|
|
1321
1515
|
"ensure" => always_run,
|
|
1322
|
-
_ => BranchRole::branched(main_body_anchor(parent)?).escaping(),
|
|
1516
|
+
_ => BranchRole::branched(main_body_anchor(parent, index)?).escaping(),
|
|
1323
1517
|
}),
|
|
1324
1518
|
_ => None,
|
|
1325
1519
|
}
|
|
@@ -1364,23 +1558,45 @@ impl<'tree> BranchRole<'tree> {
|
|
|
1364
1558
|
}
|
|
1365
1559
|
|
|
1366
1560
|
/// The first statement of a `begin`'s main body, which stands for the whole of it.
|
|
1367
|
-
fn main_body_anchor<'tree>(
|
|
1368
|
-
|
|
1369
|
-
|
|
1561
|
+
fn main_body_anchor<'tree>(
|
|
1562
|
+
container: Node<'tree>,
|
|
1563
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
1564
|
+
) -> Option<Node<'tree>> {
|
|
1565
|
+
super::super::send_node::named_children_in(container, index)
|
|
1566
|
+
.iter()
|
|
1567
|
+
.copied()
|
|
1370
1568
|
.find(|child| !matches!(child.kind_str(), "rescue" | "else" | "ensure"))
|
|
1371
1569
|
}
|
|
1372
1570
|
|
|
1373
|
-
fn is_rescue_container
|
|
1374
|
-
|
|
1571
|
+
fn is_rescue_container<'tree>(
|
|
1572
|
+
node: Node<'tree>,
|
|
1573
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
1574
|
+
) -> bool {
|
|
1575
|
+
has_child_kind(node, "rescue", index) || has_child_kind(node, "ensure", index)
|
|
1375
1576
|
}
|
|
1376
1577
|
|
|
1377
|
-
fn has_child_kind
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1578
|
+
fn has_child_kind<'tree>(
|
|
1579
|
+
node: Node<'tree>,
|
|
1580
|
+
kind: &str,
|
|
1581
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
1582
|
+
) -> bool {
|
|
1583
|
+
super::super::send_node::named_children_in(node, index)
|
|
1584
|
+
.iter()
|
|
1585
|
+
.any(|child| child.kind_str() == kind)
|
|
1381
1586
|
}
|
|
1382
1587
|
|
|
1383
|
-
|
|
1588
|
+
/// Which field the child fills in its parent.
|
|
1589
|
+
///
|
|
1590
|
+
/// The index recorded this for every node of the file on the walk that built it, so the cursor
|
|
1591
|
+
/// walk below is only for a node from one of the extra trees `Metrics` parses.
|
|
1592
|
+
fn field_name(
|
|
1593
|
+
child: Node<'_>,
|
|
1594
|
+
parent: Node<'_>,
|
|
1595
|
+
index: &super::super::AstIndex<'_>,
|
|
1596
|
+
) -> Option<&'static str> {
|
|
1597
|
+
if index.knows(child) {
|
|
1598
|
+
return index.field_name_of(child);
|
|
1599
|
+
}
|
|
1384
1600
|
let mut cursor = parent.walk();
|
|
1385
1601
|
if !cursor.goto_first_child() {
|
|
1386
1602
|
return None;
|
|
@@ -1433,6 +1649,21 @@ fn opaque_binding_argument(node: Node<'_>) -> bool {
|
|
|
1433
1649
|
}
|
|
1434
1650
|
}
|
|
1435
1651
|
|
|
1652
|
+
/// The node types that hold their own local variables, which a name written inside does not leave.
|
|
1653
|
+
fn opens_a_scope(node: Node<'_>) -> bool {
|
|
1654
|
+
matches!(
|
|
1655
|
+
node.kind_str(),
|
|
1656
|
+
"method"
|
|
1657
|
+
| "singleton_method"
|
|
1658
|
+
| "class"
|
|
1659
|
+
| "singleton_class"
|
|
1660
|
+
| "module"
|
|
1661
|
+
| "block"
|
|
1662
|
+
| "do_block"
|
|
1663
|
+
| "lambda"
|
|
1664
|
+
)
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1436
1667
|
fn contains_kind(node: Node<'_>, kind: &str) -> bool {
|
|
1437
1668
|
if node.kind_str() == kind {
|
|
1438
1669
|
return true;
|
|
@@ -1444,9 +1675,12 @@ fn contains_kind(node: Node<'_>, kind: &str) -> bool {
|
|
|
1444
1675
|
|
|
1445
1676
|
/// The branch node types `reference_assignments` looks for: an assignment under one of them may
|
|
1446
1677
|
/// be skipped on some iterations, so every one of them counts as read by the loop.
|
|
1447
|
-
fn has_branch_ancestor
|
|
1678
|
+
fn has_branch_ancestor<'tree>(
|
|
1679
|
+
node: Node<'tree>,
|
|
1680
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
1681
|
+
) -> bool {
|
|
1448
1682
|
let mut current = node;
|
|
1449
|
-
while let Some(parent) =
|
|
1683
|
+
while let Some(parent) = index.parent(current) {
|
|
1450
1684
|
let branching = matches!(
|
|
1451
1685
|
parent.kind_str(),
|
|
1452
1686
|
"if" | "elsif"
|
|
@@ -1458,7 +1692,8 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
|
|
|
1458
1692
|
| "case_match"
|
|
1459
1693
|
| "rescue"
|
|
1460
1694
|
| "rescue_modifier"
|
|
1461
|
-
) || (parent.kind_str() != "program"
|
|
1695
|
+
) || (parent.kind_str() != "program"
|
|
1696
|
+
&& has_child_kind(parent, "rescue", index));
|
|
1462
1697
|
if branching {
|
|
1463
1698
|
return true;
|
|
1464
1699
|
}
|
|
@@ -1472,6 +1707,7 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
|
|
|
1472
1707
|
fn collect_loop_references(
|
|
1473
1708
|
node: Node<'_>,
|
|
1474
1709
|
source: &SourceFile,
|
|
1710
|
+
index: &super::super::AstIndex<'_>,
|
|
1475
1711
|
names: &mut Vec<String>,
|
|
1476
1712
|
assignments: &mut HashSet<String>,
|
|
1477
1713
|
) {
|
|
@@ -1514,15 +1750,15 @@ fn collect_loop_references(
|
|
|
1514
1750
|
// target all wear the same node type here. A `for` variable and a rescue clause's
|
|
1515
1751
|
// variable are `lvasgn` nodes that carry no value.
|
|
1516
1752
|
"identifier" => {
|
|
1517
|
-
if is_variable_read(child) {
|
|
1753
|
+
if is_variable_read(child, index) {
|
|
1518
1754
|
names.push(source.node_text(child).to_owned());
|
|
1519
|
-
} else if bare_assignment_target(child) {
|
|
1755
|
+
} else if bare_assignment_target(child, index) {
|
|
1520
1756
|
assignments.insert(shape(child, None, source));
|
|
1521
1757
|
}
|
|
1522
1758
|
}
|
|
1523
1759
|
_ => {}
|
|
1524
1760
|
}
|
|
1525
|
-
collect_loop_references(child, source, names, assignments);
|
|
1761
|
+
collect_loop_references(child, source, index, names, assignments);
|
|
1526
1762
|
}
|
|
1527
1763
|
}
|
|
1528
1764
|
|
|
@@ -1557,15 +1793,15 @@ fn identifier_words(text: &str) -> Vec<String> {
|
|
|
1557
1793
|
|
|
1558
1794
|
/// Whether an identifier stands where the parser upstream would have built an `lvar`, rather than
|
|
1559
1795
|
/// a name being declared, written or called.
|
|
1560
|
-
pub(super) fn is_variable_read(node: Node<'_>) -> bool {
|
|
1561
|
-
let Some(parent) =
|
|
1796
|
+
pub(super) fn is_variable_read(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
|
|
1797
|
+
let Some(parent) = index.parent(node) else {
|
|
1562
1798
|
return true;
|
|
1563
1799
|
};
|
|
1564
1800
|
match parent.kind_str() {
|
|
1565
1801
|
// The receiver of `foo.bar` and of `def obj.baz` is read; the selector is not.
|
|
1566
|
-
"call" => field_name(node, parent) != Some("method"),
|
|
1567
|
-
"method" | "singleton_method" => field_name(node, parent) != Some("name"),
|
|
1568
|
-
"assignment" | "operator_assignment" => field_name(node, parent) != Some("left"),
|
|
1802
|
+
"call" => field_name(node, parent, index) != Some("method"),
|
|
1803
|
+
"method" | "singleton_method" => field_name(node, parent, index) != Some("name"),
|
|
1804
|
+
"assignment" | "operator_assignment" => field_name(node, parent, index) != Some("left"),
|
|
1569
1805
|
"left_assignment_list"
|
|
1570
1806
|
| "rest_assignment"
|
|
1571
1807
|
| "destructured_left_assignment"
|
|
@@ -1582,8 +1818,8 @@ pub(super) fn is_variable_read(node: Node<'_>) -> bool {
|
|
|
1582
1818
|
| "keyword_parameter"
|
|
1583
1819
|
| "splat_parameter"
|
|
1584
1820
|
| "hash_splat_parameter"
|
|
1585
|
-
| "block_parameter" => field_name(node, parent) != Some("name"),
|
|
1586
|
-
"for" => field_name(node, parent) != Some("pattern"),
|
|
1821
|
+
| "block_parameter" => field_name(node, parent, index) != Some("name"),
|
|
1822
|
+
"for" => field_name(node, parent, index) != Some("pattern"),
|
|
1587
1823
|
_ => true,
|
|
1588
1824
|
}
|
|
1589
1825
|
}
|
|
@@ -1600,12 +1836,12 @@ fn swallowed_by_escape(node: Node<'_>, source: &SourceFile) -> bool {
|
|
|
1600
1836
|
}
|
|
1601
1837
|
|
|
1602
1838
|
/// Whether an identifier is a write that stores no value of its own.
|
|
1603
|
-
fn bare_assignment_target(node: Node<'_>) -> bool {
|
|
1604
|
-
let Some(parent) =
|
|
1839
|
+
fn bare_assignment_target(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
|
|
1840
|
+
let Some(parent) = index.parent(node) else {
|
|
1605
1841
|
return false;
|
|
1606
1842
|
};
|
|
1607
1843
|
match parent.kind_str() {
|
|
1608
|
-
"for" => field_name(node, parent) == Some("pattern"),
|
|
1844
|
+
"for" => field_name(node, parent, index) == Some("pattern"),
|
|
1609
1845
|
"exception_variable" => true,
|
|
1610
1846
|
_ => false,
|
|
1611
1847
|
}
|
|
@@ -1674,9 +1910,13 @@ fn named_captures(source: &str) -> Vec<String> {
|
|
|
1674
1910
|
|
|
1675
1911
|
/// `in_modifier_conditional?`: an assignment made in `foo = 1 if bar` is not in scope to the left
|
|
1676
1912
|
/// of the keyword, so a read there cannot be the one that uses it.
|
|
1677
|
-
fn in_modifier_conditional(
|
|
1913
|
+
fn in_modifier_conditional(
|
|
1914
|
+
assignment: Node<'_>,
|
|
1915
|
+
reference: Node<'_>,
|
|
1916
|
+
index: &super::super::AstIndex<'_>,
|
|
1917
|
+
) -> bool {
|
|
1678
1918
|
let mut current = assignment;
|
|
1679
|
-
while let Some(parent) =
|
|
1919
|
+
while let Some(parent) = index.parent(current) {
|
|
1680
1920
|
if matches!(
|
|
1681
1921
|
parent.kind_str(),
|
|
1682
1922
|
"if_modifier" | "unless_modifier" | "while_modifier" | "until_modifier"
|
|
@@ -1697,10 +1937,13 @@ fn covers(container: Node<'_>, node: Node<'_>) -> bool {
|
|
|
1697
1937
|
|
|
1698
1938
|
/// Each heredoc's body, paired with the `<<~X` that opened it. Both appear in the same order in
|
|
1699
1939
|
/// the file, which is what makes matching them by position sound.
|
|
1700
|
-
fn heredoc_bodies
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1940
|
+
fn heredoc_bodies<'tree>(
|
|
1941
|
+
index: &super::super::AstIndex<'tree>,
|
|
1942
|
+
) -> crate::rules::IdKeyed<Node<'tree>> {
|
|
1943
|
+
// The index already groups the file's nodes by kind, so the recursive walk this used to make
|
|
1944
|
+
// -- which allocated a vector of children at every node of the tree -- answers nothing new.
|
|
1945
|
+
let mut beginnings: Vec<Node<'tree>> = index.nodes_of_kind("heredoc_beginning").collect();
|
|
1946
|
+
let mut bodies: Vec<Node<'tree>> = index.nodes_of_kind("heredoc_body").collect();
|
|
1704
1947
|
beginnings.sort_by_key(|node: &Node<'_>| node.start_byte());
|
|
1705
1948
|
bodies.sort_by_key(|node: &Node<'_>| node.start_byte());
|
|
1706
1949
|
beginnings
|
|
@@ -1709,18 +1952,3 @@ fn heredoc_bodies(root: Node<'_>) -> HashMap<usize, Node<'_>> {
|
|
|
1709
1952
|
.map(|(beginning, body)| (beginning.id(), body))
|
|
1710
1953
|
.collect()
|
|
1711
1954
|
}
|
|
1712
|
-
|
|
1713
|
-
fn collect_heredocs<'tree>(
|
|
1714
|
-
node: Node<'tree>,
|
|
1715
|
-
beginnings: &mut Vec<Node<'tree>>,
|
|
1716
|
-
bodies: &mut Vec<Node<'tree>>,
|
|
1717
|
-
) {
|
|
1718
|
-
match node.kind_str() {
|
|
1719
|
-
"heredoc_beginning" => beginnings.push(node),
|
|
1720
|
-
"heredoc_body" => bodies.push(node),
|
|
1721
|
-
_ => {}
|
|
1722
|
-
}
|
|
1723
|
-
for child in named_children(node) {
|
|
1724
|
-
collect_heredocs(child, beginnings, bodies);
|
|
1725
|
-
}
|
|
1726
|
-
}
|