sonicop 26.8.112 → 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/Cargo.lock +11 -11
- data/Cargo.toml +4 -4
- data/README.ja.md +36 -23
- data/README.md +38 -26
- data/lib/sonicop/version.rb +1 -1
- data/src/config/inheritance.rs +45 -3
- data/src/engine.rs +11 -7
- data/src/lib.rs +1 -0
- data/src/nul_bytes.rs +2 -6
- data/src/parser.rs +37 -0
- data/src/profile.rs +13 -1
- 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 +3 -2
- data/src/rules/layout/array_alignment.rs +1 -2
- data/src/rules/layout/block_end_newline.rs +5 -5
- data/src/rules/layout/class_structure.rs +5 -3
- data/src/rules/layout/closing_heredoc_indentation.rs +4 -3
- data/src/rules/layout/closing_parenthesis_indentation.rs +1 -2
- data/src/rules/layout/condition_position.rs +3 -2
- data/src/rules/layout/def_end_alignment.rs +2 -2
- data/src/rules/layout/dot_position.rs +1 -2
- data/src/rules/layout/empty_line_after_guard_clause.rs +3 -6
- data/src/rules/layout/empty_line_after_multiline_condition.rs +5 -5
- data/src/rules/layout/empty_lines_around_access_modifier.rs +2 -3
- data/src/rules/layout/empty_lines_around_arguments.rs +4 -4
- 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 +2 -2
- data/src/rules/layout/extra_spacing.rs +7 -6
- 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 +4 -4
- 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 +4 -3
- data/src/rules/layout/indentation_width.rs +14 -13
- data/src/rules/layout/line_continuation_leading_space.rs +4 -3
- data/src/rules/layout/line_continuation_spacing.rs +2 -2
- data/src/rules/layout/line_length.rs +101 -57
- data/src/rules/layout/multiline_assignment_layout.rs +4 -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 +3 -2
- data/src/rules/layout/single_line_block_chain.rs +4 -3
- data/src/rules/layout/space_around_block_parameters.rs +1 -2
- data/src/rules/layout/space_around_equals_in_parameter_default.rs +1 -2
- data/src/rules/layout/space_around_operators.rs +1 -2
- data/src/rules/layout/space_before_brackets.rs +6 -5
- data/src/rules/layout/space_before_comma.rs +3 -2
- data/src/rules/layout/space_inside_range_literal.rs +8 -6
- data/src/rules/layout/support.rs +9 -7
- data/src/rules/layout/tokens.rs +15 -6
- data/src/rules/lint/access_modifier.rs +19 -10
- data/src/rules/lint/ambiguity.rs +2 -2
- data/src/rules/lint/ambiguous_block_association.rs +7 -10
- data/src/rules/lint/ambiguous_regexp_literal.rs +7 -5
- data/src/rules/lint/array_literal_in_regexp.rs +4 -3
- 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 +2 -1
- 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 +6 -5
- data/src/rules/lint/duplicate_case_condition.rs +5 -5
- data/src/rules/lint/duplicate_hash_key.rs +4 -3
- data/src/rules/lint/duplicate_match_pattern.rs +7 -5
- data/src/rules/lint/duplicate_methods.rs +4 -3
- data/src/rules/lint/duplicate_require.rs +1 -2
- data/src/rules/lint/duplicate_set_element.rs +3 -2
- data/src/rules/lint/empty_conditional_body.rs +4 -3
- 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/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 +7 -5
- 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 +5 -3
- data/src/rules/lint/literal_assignment_in_condition.rs +4 -3
- data/src/rules/lint/literal_in_interpolation.rs +5 -5
- data/src/rules/lint/literals.rs +10 -8
- 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 +1 -1
- data/src/rules/lint/non_deterministic_require_order.rs +5 -3
- data/src/rules/lint/non_local_exit_from_iterator.rs +3 -2
- 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/redundant_dir_glob_sort.rs +1 -2
- data/src/rules/lint/redundant_safe_navigation.rs +4 -3
- data/src/rules/lint/redundant_splat_expansion.rs +4 -2
- data/src/rules/lint/redundant_string_coercion.rs +3 -2
- data/src/rules/lint/regexp_source.rs +2 -2
- 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 +2 -2
- data/src/rules/lint/safe_navigation_chain.rs +3 -2
- data/src/rules/lint/self_assignment.rs +3 -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_in_number_conversion.rs +4 -3
- data/src/rules/lint/symbol_conversion.rs +6 -5
- data/src/rules/lint/syntax.rs +4 -3
- 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 +3 -2
- data/src/rules/lint/unmodified_reduce_accumulator.rs +7 -6
- data/src/rules/lint/unreachable_pattern_branch.rs +4 -4
- 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 +146 -100
- data/src/rules/lint/void.rs +5 -4
- data/src/rules/metrics/block_nesting.rs +1 -2
- data/src/rules/metrics/collection_literal_length.rs +2 -1
- data/src/rules/metrics/complexity.rs +18 -13
- data/src/rules/metrics/fragments.rs +10 -14
- data/src/rules/metrics/locals.rs +23 -12
- data/src/rules/metrics/parameter_lists.rs +7 -6
- data/src/rules/metrics/support.rs +1 -1
- data/src/rules/mod.rs +464 -62
- data/src/rules/naming/ascii_identifiers.rs +3 -3
- data/src/rules/naming/block_forwarding.rs +9 -8
- data/src/rules/naming/constant_name.rs +1 -1
- data/src/rules/naming/predicate_method.rs +6 -5
- data/src/rules/naming/predicate_prefix.rs +10 -8
- data/src/rules/naming/rescued_exceptions_variable_name.rs +8 -6
- data/src/rules/naming/support.rs +7 -5
- data/src/rules/naming/variable_number.rs +4 -4
- data/src/rules/node_ext.rs +1 -1
- data/src/rules/ruby_literal.rs +3 -2
- 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 +1 -1
- data/src/rules/style/alias.rs +1 -2
- 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 +3 -3
- data/src/rules/style/array_intersect.rs +2 -2
- data/src/rules/style/array_intersect_with_single_element.rs +1 -1
- 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_delimiters.rs +7 -6
- data/src/rules/style/case_equality.rs +3 -2
- data/src/rules/style/case_like_if.rs +1 -1
- data/src/rules/style/class_and_module_children.rs +7 -5
- data/src/rules/style/class_methods.rs +1 -1
- 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 +1 -1
- 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 +8 -1
- data/src/rules/style/constant_visibility.rs +3 -3
- data/src/rules/style/copyright.rs +1 -1
- 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 +1 -1
- 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 +1 -1
- data/src/rules/style/empty_lambda_parameter.rs +1 -1
- data/src/rules/style/empty_literal.rs +2 -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 +1 -1
- data/src/rules/style/env_home.rs +1 -1
- 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_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_string.rs +2 -2
- data/src/rules/style/format_string_token.rs +6 -6
- 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 +3 -3
- data/src/rules/style/hash_subset.rs +16 -10
- data/src/rules/style/hash_syntax.rs +4 -4
- data/src/rules/style/hash_transform.rs +20 -18
- data/src/rules/style/identical_conditional_branches.rs +4 -5
- data/src/rules/style/if_inside_else.rs +3 -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 +1 -1
- data/src/rules/style/in_pattern_then.rs +2 -2
- data/src/rules/style/inverse_methods.rs +2 -2
- data/src/rules/style/invertible_unless_condition.rs +3 -3
- data/src/rules/style/it_block_parameter.rs +1 -1
- data/src/rules/style/keyword_arguments_merging.rs +2 -2
- data/src/rules/style/keyword_parameters_order.rs +1 -1
- data/src/rules/style/magic_comment_format.rs +3 -2
- 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 +1 -1
- data/src/rules/style/multiline_block_chain.rs +2 -2
- data/src/rules/style/multiline_if_then.rs +1 -1
- data/src/rules/style/multiline_in_pattern_then.rs +1 -1
- 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 +3 -3
- data/src/rules/style/negated_if.rs +2 -2
- data/src/rules/style/negated_if_else_condition.rs +2 -2
- data/src/rules/style/negated_while.rs +1 -1
- data/src/rules/style/negative_array_index.rs +2 -2
- 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 +1 -1
- 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 +2 -2
- data/src/rules/style/numbered_parameters_limit.rs +2 -2
- data/src/rules/style/one_line_conditional.rs +4 -4
- 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 +9 -10
- 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 +9 -7
- data/src/rules/style/percent_q_literals.rs +4 -3
- data/src/rules/style/preferred_hash_methods.rs +1 -1
- 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 +2 -2
- data/src/rules/style/redundant_capital_w.rs +3 -2
- data/src/rules/style/redundant_condition.rs +9 -7
- data/src/rules/style/redundant_current_directory_in_path.rs +2 -2
- data/src/rules/style/redundant_double_splat_hash_braces.rs +4 -4
- data/src/rules/style/redundant_file_extension_in_require.rs +2 -2
- data/src/rules/style/redundant_format.rs +16 -14
- 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 +2 -2
- data/src/rules/style/redundant_interpolation.rs +2 -2
- data/src/rules/style/redundant_interpolation_unfreeze.rs +2 -2
- data/src/rules/style/redundant_line_continuation.rs +5 -4
- data/src/rules/style/redundant_parentheses.rs +16 -15
- 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 +11 -11
- 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 +1 -1
- 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/sample.rs +3 -3
- data/src/rules/style/select_by.rs +2 -2
- data/src/rules/style/select_by_range.rs +1 -1
- data/src/rules/style/semicolon.rs +5 -4
- data/src/rules/style/single_argument_dig.rs +1 -1
- data/src/rules/style/single_line_block_params.rs +7 -6
- 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 +1 -1
- data/src/rules/style/string_concatenation.rs +9 -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 +1 -1
- data/src/rules/style/struct_inheritance.rs +3 -3
- data/src/rules/style/super_arguments.rs +2 -2
- data/src/rules/style/super_with_args_parentheses.rs +1 -1
- data/src/rules/style/swap_values.rs +1 -1
- data/src/rules/style/symbol_array.rs +1 -1
- 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 +4 -4
- data/src/rules/style/top_level_method_definition.rs +1 -1
- data/src/rules/style/trailing_body.rs +1 -1
- data/src/rules/style/trailing_comma_in_array_literal.rs +1 -1
- data/src/rules/style/trailing_comma_in_hash_literal.rs +1 -1
- data/src/rules/style/trailing_method_end_statement.rs +1 -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 +1 -1
- 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 +3 -3
- data/src/rules/style/yoda_condition.rs +2 -2
- data/src/rules/support.rs +33 -8
- metadata +2 -1
|
@@ -136,28 +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,
|
|
146
149
|
/// The references Naming handlers see. This includes implicit numbered block parameters,
|
|
147
150
|
/// which are local-variable reads but are not declarations VariableForce reports.
|
|
148
|
-
naming_references:
|
|
151
|
+
naming_references: crate::rules::IdSet,
|
|
149
152
|
/// The `foo()` calls whose name a local variable already holds. The parentheses make these
|
|
150
153
|
/// calls whatever the name resolves to, so nothing here is an `lvar` -- but writing the same
|
|
151
154
|
/// name without them would be one, which is the question a cop that drops parentheses asks.
|
|
152
|
-
shadowed_calls:
|
|
155
|
+
shadowed_calls: crate::rules::IdSet,
|
|
153
156
|
/// Receiverless call-name nodes whose spelling is already held by a local variable. Unlike
|
|
154
157
|
/// `shadowed_calls`, this also records calls with arguments for syntax recovery such as
|
|
155
158
|
/// `collection [0]`.
|
|
156
|
-
local_method_names:
|
|
159
|
+
local_method_names: crate::rules::IdSet,
|
|
157
160
|
/// The identifier nodes that RuboCop dispatches as variable definitions. Pattern bindings and
|
|
158
161
|
/// explicit block-local variables deliberately stay out: upstream names them `match_var` and
|
|
159
162
|
/// `shadowarg`, neither of which the Naming cops handle.
|
|
160
|
-
naming_definitions:
|
|
163
|
+
naming_definitions: crate::rules::IdSet,
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
impl<'tree> Analysis<'tree> {
|
|
@@ -193,7 +196,7 @@ impl<'tree> Analysis<'tree> {
|
|
|
193
196
|
/// declarations are naming definitions too, but an AST walk restricted to `lvasgn` does not
|
|
194
197
|
/// visit them.
|
|
195
198
|
pub(in crate::rules) fn is_local_assignment(&self, node: Node<'_>) -> bool {
|
|
196
|
-
self.is_naming_definition(node) && structural_definition(node)
|
|
199
|
+
self.is_naming_definition(node) && structural_definition(node, self.index)
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
/// Whether the parser would dispatch this node to a Naming variable-definition handler.
|
|
@@ -204,33 +207,39 @@ impl<'tree> Analysis<'tree> {
|
|
|
204
207
|
matches!(
|
|
205
208
|
node.kind_str(),
|
|
206
209
|
"instance_variable" | "class_variable" | "global_variable"
|
|
207
|
-
) && structural_definition(node)
|
|
210
|
+
) && structural_definition(node, self.index)
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
pub(in crate::rules) fn is_definition(&self, node: Node<'_>) -> bool {
|
|
211
214
|
self.is_naming_definition(node)
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
pub(in crate::rules) fn run(
|
|
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();
|
|
215
222
|
let mut force = Force {
|
|
223
|
+
index,
|
|
216
224
|
source,
|
|
217
225
|
scopes: Vec::new(),
|
|
218
226
|
variables: Vec::new(),
|
|
219
227
|
stack: Vec::new(),
|
|
220
228
|
branches: Vec::new(),
|
|
221
229
|
branch_index: HashMap::new(),
|
|
222
|
-
heredocs: heredoc_bodies(
|
|
223
|
-
scanned:
|
|
224
|
-
lvars:
|
|
225
|
-
naming_references:
|
|
226
|
-
shadowed_calls:
|
|
227
|
-
local_method_names:
|
|
228
|
-
naming_definitions:
|
|
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(),
|
|
229
237
|
};
|
|
230
238
|
force.push_scope(root, true);
|
|
231
239
|
force.process_children(root);
|
|
232
240
|
force.pop_scope();
|
|
233
241
|
Analysis {
|
|
242
|
+
index,
|
|
234
243
|
scopes: force.scopes,
|
|
235
244
|
variables: force.variables,
|
|
236
245
|
lvars: force.lvars,
|
|
@@ -263,6 +272,7 @@ struct Frame<'tree> {
|
|
|
263
272
|
}
|
|
264
273
|
|
|
265
274
|
struct Force<'tree, 'a> {
|
|
275
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
266
276
|
source: &'a SourceFile,
|
|
267
277
|
scopes: Vec<Scope<'tree>>,
|
|
268
278
|
variables: Vec<Variable<'tree>>,
|
|
@@ -272,14 +282,14 @@ struct Force<'tree, 'a> {
|
|
|
272
282
|
/// Each heredoc's body, found from the `<<~X` that opened it. tree-sitter hangs the body off
|
|
273
283
|
/// the enclosing statement, so a heredoc written inside a block would otherwise have its
|
|
274
284
|
/// interpolations resolved in the wrong scope.
|
|
275
|
-
heredocs:
|
|
285
|
+
heredocs: crate::rules::IdKeyed<Node<'tree>>,
|
|
276
286
|
/// Nodes already walked in an outer scope, which the scope they sit in must not walk again.
|
|
277
|
-
scanned:
|
|
278
|
-
lvars:
|
|
279
|
-
naming_references:
|
|
280
|
-
shadowed_calls:
|
|
281
|
-
local_method_names:
|
|
282
|
-
naming_definitions:
|
|
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,
|
|
283
293
|
}
|
|
284
294
|
|
|
285
295
|
// ---------------------------------------------------------------------------
|
|
@@ -305,8 +315,8 @@ fn mislexed_match_operator(node: Node<'_>, source: &SourceFile) -> bool {
|
|
|
305
315
|
/// definition. Local identifiers are recorded during the force walk, but instance, class and
|
|
306
316
|
/// global variables do not participate in local-variable resolution and are cheaper to classify
|
|
307
317
|
/// directly from their parent.
|
|
308
|
-
fn structural_definition(node: Node<'_>) -> bool {
|
|
309
|
-
let Some(parent) =
|
|
318
|
+
fn structural_definition(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
|
|
319
|
+
let Some(parent) = index.parent(node) else {
|
|
310
320
|
return false;
|
|
311
321
|
};
|
|
312
322
|
match parent.kind_str() {
|
|
@@ -421,20 +431,33 @@ pub(super) fn body_node<'tree>(scope: &Scope<'tree>) -> Option<Node<'tree>> {
|
|
|
421
431
|
}
|
|
422
432
|
|
|
423
433
|
/// The call a block was written on, which names the method the block was passed to.
|
|
424
|
-
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>> {
|
|
425
438
|
match scope_node.kind_str() {
|
|
426
|
-
"block" | "do_block" =>
|
|
439
|
+
"block" | "do_block" => index
|
|
440
|
+
.parent_in_tree(scope_node)
|
|
441
|
+
.filter(|node| node.kind_str() == "call"),
|
|
427
442
|
_ => None,
|
|
428
443
|
}
|
|
429
444
|
}
|
|
430
445
|
|
|
431
446
|
/// `BlockNode#lambda?`: both `->() {}` and `lambda {}` reach RuboCop as a block on `lambda`.
|
|
432
|
-
pub(super) fn is_lambda(
|
|
433
|
-
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")
|
|
434
453
|
}
|
|
435
454
|
|
|
436
|
-
pub(super) fn block_method<'a>(
|
|
437
|
-
|
|
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)?;
|
|
438
461
|
Some(source.node_text(call.field("method")?))
|
|
439
462
|
}
|
|
440
463
|
|
|
@@ -471,19 +494,18 @@ impl<'tree> Force<'tree, '_> {
|
|
|
471
494
|
});
|
|
472
495
|
}
|
|
473
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
|
+
|
|
474
503
|
fn process_children(&mut self, node: Node<'tree>) {
|
|
475
|
-
let
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
loop {
|
|
480
|
-
let child = cursor.node();
|
|
481
|
-
if child.is_named() && !self.scanned.contains(&child.id()) {
|
|
504
|
+
let children = self.children(node);
|
|
505
|
+
for child in children.iter().copied() {
|
|
506
|
+
if !self.scanned.contains(&child.id()) {
|
|
482
507
|
self.process_node(child);
|
|
483
508
|
}
|
|
484
|
-
if !cursor.goto_next_sibling() {
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
509
|
}
|
|
488
510
|
}
|
|
489
511
|
|
|
@@ -550,8 +572,9 @@ impl<'tree> Force<'tree, '_> {
|
|
|
550
572
|
/// block node holding both the parameters and the body, so the inner node is not a scope.
|
|
551
573
|
fn inline_block(&self, node: Node<'tree>) -> bool {
|
|
552
574
|
node.kind_str() == "block"
|
|
553
|
-
&&
|
|
554
|
-
.
|
|
575
|
+
&& self
|
|
576
|
+
.index
|
|
577
|
+
.parent(node)
|
|
555
578
|
.is_some_and(|parent| parent.kind_str() == "lambda")
|
|
556
579
|
}
|
|
557
580
|
|
|
@@ -724,7 +747,7 @@ impl<'tree> Force<'tree, '_> {
|
|
|
724
747
|
.push(node);
|
|
725
748
|
}
|
|
726
749
|
let assignment_node = self.variables[variable].assignments[index].node;
|
|
727
|
-
if in_modifier_conditional(assignment_node, node) {
|
|
750
|
+
if in_modifier_conditional(assignment_node, node, self.index) {
|
|
728
751
|
continue;
|
|
729
752
|
}
|
|
730
753
|
let Some(branch) = branch else { break };
|
|
@@ -879,10 +902,10 @@ impl<'tree> Force<'tree, '_> {
|
|
|
879
902
|
// Only tree-sitter spells a method name with the same node type as a variable read; the
|
|
880
903
|
// parser upstream turns `def foo` and `alias foo bar` into symbols. The receiver of
|
|
881
904
|
// `def obj.foo` is a genuine read and stays.
|
|
882
|
-
if
|
|
905
|
+
if self.index.parent(node).is_some_and(|parent| {
|
|
883
906
|
matches!(parent.kind_str(), "alias" | "undef" | "setter")
|
|
884
907
|
|| (matches!(parent.kind_str(), "method" | "singleton_method")
|
|
885
|
-
&& field_name(node, parent) == Some("name"))
|
|
908
|
+
&& field_name(node, parent, self.index) == Some("name"))
|
|
886
909
|
}) {
|
|
887
910
|
return;
|
|
888
911
|
}
|
|
@@ -1331,7 +1354,7 @@ impl<'tree> Force<'tree, '_> {
|
|
|
1331
1354
|
fn mark_assignments_referenced_in_loop(&mut self, node: Node<'tree>) {
|
|
1332
1355
|
let mut names = Vec::new();
|
|
1333
1356
|
let mut assignments = HashSet::new();
|
|
1334
|
-
collect_loop_references(node, self.source, &mut names, &mut assignments);
|
|
1357
|
+
collect_loop_references(node, self.source, self.index, &mut names, &mut assignments);
|
|
1335
1358
|
for name in names {
|
|
1336
1359
|
let Some(variable) = self.find_variable(&name) else {
|
|
1337
1360
|
continue;
|
|
@@ -1350,7 +1373,7 @@ impl<'tree> Force<'tree, '_> {
|
|
|
1350
1373
|
};
|
|
1351
1374
|
for &index in &indices {
|
|
1352
1375
|
let assignment = &self.variables[variable].assignments[index];
|
|
1353
|
-
if has_branch_ancestor(assignment.node) {
|
|
1376
|
+
if has_branch_ancestor(assignment.node, self.index) {
|
|
1354
1377
|
self.variables[variable].assignments[index].referenced = true;
|
|
1355
1378
|
}
|
|
1356
1379
|
}
|
|
@@ -1379,8 +1402,8 @@ impl<'tree> Force<'tree, '_> {
|
|
|
1379
1402
|
if !top_level && current.id() == scope_node.id() {
|
|
1380
1403
|
return None;
|
|
1381
1404
|
}
|
|
1382
|
-
let parent =
|
|
1383
|
-
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)
|
|
1384
1407
|
&& role.branched
|
|
1385
1408
|
{
|
|
1386
1409
|
return Some(self.intern_branch(role, parent, scope_node, top_level));
|
|
@@ -1451,8 +1474,12 @@ fn implicit_numbered_parameter(name: &str) -> bool {
|
|
|
1451
1474
|
|
|
1452
1475
|
/// Whether the child stands in a branch of the control structure above it, and how that branch
|
|
1453
1476
|
/// behaves: `(may_run_incompletely, may_jump_to_other_branch, branched)`.
|
|
1454
|
-
fn branch_role<'tree>(
|
|
1455
|
-
|
|
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);
|
|
1456
1483
|
let always_run = BranchRole::always_run(child);
|
|
1457
1484
|
let branched = BranchRole::branched(child);
|
|
1458
1485
|
match parent.kind_str() {
|
|
@@ -1483,10 +1510,10 @@ fn branch_role<'tree>(child: Node<'tree>, parent: Node<'tree>) -> Option<BranchR
|
|
|
1483
1510
|
// `begin … rescue … ensure … end` has no node of its own here: the clauses stand beside
|
|
1484
1511
|
// the statements they guard. Upstream wraps those statements in one node, and the whole
|
|
1485
1512
|
// main body is a single branch, so they all have to point at the same child.
|
|
1486
|
-
_ if is_rescue_container(parent) => Some(match child.kind_str() {
|
|
1513
|
+
_ if is_rescue_container(parent, index) => Some(match child.kind_str() {
|
|
1487
1514
|
"rescue" | "else" => branched,
|
|
1488
1515
|
"ensure" => always_run,
|
|
1489
|
-
_ => BranchRole::branched(main_body_anchor(parent)?).escaping(),
|
|
1516
|
+
_ => BranchRole::branched(main_body_anchor(parent, index)?).escaping(),
|
|
1490
1517
|
}),
|
|
1491
1518
|
_ => None,
|
|
1492
1519
|
}
|
|
@@ -1531,23 +1558,45 @@ impl<'tree> BranchRole<'tree> {
|
|
|
1531
1558
|
}
|
|
1532
1559
|
|
|
1533
1560
|
/// The first statement of a `begin`'s main body, which stands for the whole of it.
|
|
1534
|
-
fn main_body_anchor<'tree>(
|
|
1535
|
-
|
|
1536
|
-
|
|
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()
|
|
1537
1568
|
.find(|child| !matches!(child.kind_str(), "rescue" | "else" | "ensure"))
|
|
1538
1569
|
}
|
|
1539
1570
|
|
|
1540
|
-
fn is_rescue_container
|
|
1541
|
-
|
|
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)
|
|
1542
1576
|
}
|
|
1543
1577
|
|
|
1544
|
-
fn has_child_kind
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
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)
|
|
1548
1586
|
}
|
|
1549
1587
|
|
|
1550
|
-
|
|
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
|
+
}
|
|
1551
1600
|
let mut cursor = parent.walk();
|
|
1552
1601
|
if !cursor.goto_first_child() {
|
|
1553
1602
|
return None;
|
|
@@ -1626,9 +1675,12 @@ fn contains_kind(node: Node<'_>, kind: &str) -> bool {
|
|
|
1626
1675
|
|
|
1627
1676
|
/// The branch node types `reference_assignments` looks for: an assignment under one of them may
|
|
1628
1677
|
/// be skipped on some iterations, so every one of them counts as read by the loop.
|
|
1629
|
-
fn has_branch_ancestor
|
|
1678
|
+
fn has_branch_ancestor<'tree>(
|
|
1679
|
+
node: Node<'tree>,
|
|
1680
|
+
index: &'tree super::super::AstIndex<'tree>,
|
|
1681
|
+
) -> bool {
|
|
1630
1682
|
let mut current = node;
|
|
1631
|
-
while let Some(parent) =
|
|
1683
|
+
while let Some(parent) = index.parent(current) {
|
|
1632
1684
|
let branching = matches!(
|
|
1633
1685
|
parent.kind_str(),
|
|
1634
1686
|
"if" | "elsif"
|
|
@@ -1640,7 +1692,8 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
|
|
|
1640
1692
|
| "case_match"
|
|
1641
1693
|
| "rescue"
|
|
1642
1694
|
| "rescue_modifier"
|
|
1643
|
-
) || (parent.kind_str() != "program"
|
|
1695
|
+
) || (parent.kind_str() != "program"
|
|
1696
|
+
&& has_child_kind(parent, "rescue", index));
|
|
1644
1697
|
if branching {
|
|
1645
1698
|
return true;
|
|
1646
1699
|
}
|
|
@@ -1654,6 +1707,7 @@ fn has_branch_ancestor(node: Node<'_>) -> bool {
|
|
|
1654
1707
|
fn collect_loop_references(
|
|
1655
1708
|
node: Node<'_>,
|
|
1656
1709
|
source: &SourceFile,
|
|
1710
|
+
index: &super::super::AstIndex<'_>,
|
|
1657
1711
|
names: &mut Vec<String>,
|
|
1658
1712
|
assignments: &mut HashSet<String>,
|
|
1659
1713
|
) {
|
|
@@ -1696,15 +1750,15 @@ fn collect_loop_references(
|
|
|
1696
1750
|
// target all wear the same node type here. A `for` variable and a rescue clause's
|
|
1697
1751
|
// variable are `lvasgn` nodes that carry no value.
|
|
1698
1752
|
"identifier" => {
|
|
1699
|
-
if is_variable_read(child) {
|
|
1753
|
+
if is_variable_read(child, index) {
|
|
1700
1754
|
names.push(source.node_text(child).to_owned());
|
|
1701
|
-
} else if bare_assignment_target(child) {
|
|
1755
|
+
} else if bare_assignment_target(child, index) {
|
|
1702
1756
|
assignments.insert(shape(child, None, source));
|
|
1703
1757
|
}
|
|
1704
1758
|
}
|
|
1705
1759
|
_ => {}
|
|
1706
1760
|
}
|
|
1707
|
-
collect_loop_references(child, source, names, assignments);
|
|
1761
|
+
collect_loop_references(child, source, index, names, assignments);
|
|
1708
1762
|
}
|
|
1709
1763
|
}
|
|
1710
1764
|
|
|
@@ -1739,15 +1793,15 @@ fn identifier_words(text: &str) -> Vec<String> {
|
|
|
1739
1793
|
|
|
1740
1794
|
/// Whether an identifier stands where the parser upstream would have built an `lvar`, rather than
|
|
1741
1795
|
/// a name being declared, written or called.
|
|
1742
|
-
pub(super) fn is_variable_read(node: Node<'_>) -> bool {
|
|
1743
|
-
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 {
|
|
1744
1798
|
return true;
|
|
1745
1799
|
};
|
|
1746
1800
|
match parent.kind_str() {
|
|
1747
1801
|
// The receiver of `foo.bar` and of `def obj.baz` is read; the selector is not.
|
|
1748
|
-
"call" => field_name(node, parent) != Some("method"),
|
|
1749
|
-
"method" | "singleton_method" => field_name(node, parent) != Some("name"),
|
|
1750
|
-
"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"),
|
|
1751
1805
|
"left_assignment_list"
|
|
1752
1806
|
| "rest_assignment"
|
|
1753
1807
|
| "destructured_left_assignment"
|
|
@@ -1764,8 +1818,8 @@ pub(super) fn is_variable_read(node: Node<'_>) -> bool {
|
|
|
1764
1818
|
| "keyword_parameter"
|
|
1765
1819
|
| "splat_parameter"
|
|
1766
1820
|
| "hash_splat_parameter"
|
|
1767
|
-
| "block_parameter" => field_name(node, parent) != Some("name"),
|
|
1768
|
-
"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"),
|
|
1769
1823
|
_ => true,
|
|
1770
1824
|
}
|
|
1771
1825
|
}
|
|
@@ -1782,12 +1836,12 @@ fn swallowed_by_escape(node: Node<'_>, source: &SourceFile) -> bool {
|
|
|
1782
1836
|
}
|
|
1783
1837
|
|
|
1784
1838
|
/// Whether an identifier is a write that stores no value of its own.
|
|
1785
|
-
fn bare_assignment_target(node: Node<'_>) -> bool {
|
|
1786
|
-
let Some(parent) =
|
|
1839
|
+
fn bare_assignment_target(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
|
|
1840
|
+
let Some(parent) = index.parent(node) else {
|
|
1787
1841
|
return false;
|
|
1788
1842
|
};
|
|
1789
1843
|
match parent.kind_str() {
|
|
1790
|
-
"for" => field_name(node, parent) == Some("pattern"),
|
|
1844
|
+
"for" => field_name(node, parent, index) == Some("pattern"),
|
|
1791
1845
|
"exception_variable" => true,
|
|
1792
1846
|
_ => false,
|
|
1793
1847
|
}
|
|
@@ -1856,9 +1910,13 @@ fn named_captures(source: &str) -> Vec<String> {
|
|
|
1856
1910
|
|
|
1857
1911
|
/// `in_modifier_conditional?`: an assignment made in `foo = 1 if bar` is not in scope to the left
|
|
1858
1912
|
/// of the keyword, so a read there cannot be the one that uses it.
|
|
1859
|
-
fn in_modifier_conditional(
|
|
1913
|
+
fn in_modifier_conditional(
|
|
1914
|
+
assignment: Node<'_>,
|
|
1915
|
+
reference: Node<'_>,
|
|
1916
|
+
index: &super::super::AstIndex<'_>,
|
|
1917
|
+
) -> bool {
|
|
1860
1918
|
let mut current = assignment;
|
|
1861
|
-
while let Some(parent) =
|
|
1919
|
+
while let Some(parent) = index.parent(current) {
|
|
1862
1920
|
if matches!(
|
|
1863
1921
|
parent.kind_str(),
|
|
1864
1922
|
"if_modifier" | "unless_modifier" | "while_modifier" | "until_modifier"
|
|
@@ -1879,10 +1937,13 @@ fn covers(container: Node<'_>, node: Node<'_>) -> bool {
|
|
|
1879
1937
|
|
|
1880
1938
|
/// Each heredoc's body, paired with the `<<~X` that opened it. Both appear in the same order in
|
|
1881
1939
|
/// the file, which is what makes matching them by position sound.
|
|
1882
|
-
fn heredoc_bodies
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
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();
|
|
1886
1947
|
beginnings.sort_by_key(|node: &Node<'_>| node.start_byte());
|
|
1887
1948
|
bodies.sort_by_key(|node: &Node<'_>| node.start_byte());
|
|
1888
1949
|
beginnings
|
|
@@ -1891,18 +1952,3 @@ fn heredoc_bodies(root: Node<'_>) -> HashMap<usize, Node<'_>> {
|
|
|
1891
1952
|
.map(|(beginning, body)| (beginning.id(), body))
|
|
1892
1953
|
.collect()
|
|
1893
1954
|
}
|
|
1894
|
-
|
|
1895
|
-
fn collect_heredocs<'tree>(
|
|
1896
|
-
node: Node<'tree>,
|
|
1897
|
-
beginnings: &mut Vec<Node<'tree>>,
|
|
1898
|
-
bodies: &mut Vec<Node<'tree>>,
|
|
1899
|
-
) {
|
|
1900
|
-
match node.kind_str() {
|
|
1901
|
-
"heredoc_beginning" => beginnings.push(node),
|
|
1902
|
-
"heredoc_body" => bodies.push(node),
|
|
1903
|
-
_ => {}
|
|
1904
|
-
}
|
|
1905
|
-
for child in named_children(node) {
|
|
1906
|
-
collect_heredocs(child, beginnings, bodies);
|
|
1907
|
-
}
|
|
1908
|
-
}
|
data/src/rules/lint/void.rs
CHANGED
|
@@ -4,7 +4,7 @@ use tree_sitter::Node;
|
|
|
4
4
|
|
|
5
5
|
use crate::diagnostic::{Edit, Offense};
|
|
6
6
|
use crate::rules::RuleContext;
|
|
7
|
-
use crate::rules::send_node::
|
|
7
|
+
use crate::rules::send_node::arguments;
|
|
8
8
|
use crate::rules::support;
|
|
9
9
|
|
|
10
10
|
use super::blocks::{BLOCK_KINDS, BlockArgs};
|
|
@@ -12,6 +12,7 @@ use super::literals::{is_literal, literal_type};
|
|
|
12
12
|
use super::locals::LocalVariables;
|
|
13
13
|
use super::statements::{Branch, begin_containers, body_children, has_clause, statements};
|
|
14
14
|
use crate::rules::node_ext::NodeExt;
|
|
15
|
+
use crate::rules::send_node::named_children_of;
|
|
15
16
|
|
|
16
17
|
const SELF_MSG: &str = "`self` used in void context.";
|
|
17
18
|
|
|
@@ -341,7 +342,7 @@ impl Void<'_, '_> {
|
|
|
341
342
|
return;
|
|
342
343
|
}
|
|
343
344
|
if matches!(expression.kind_str(), "case" | "case_match") {
|
|
344
|
-
for child in
|
|
345
|
+
for child in named_children_of(expression, self.context) {
|
|
345
346
|
match child.kind_str() {
|
|
346
347
|
"when" | "in_clause" => {
|
|
347
348
|
if let Branch::One(body) = Branch::of(child.field("body")) {
|
|
@@ -560,10 +561,10 @@ impl Void<'_, '_> {
|
|
|
560
561
|
/// `entirely_literal?`.
|
|
561
562
|
fn entirely_literal(&self, node: Node<'_>) -> bool {
|
|
562
563
|
match node.kind_str() {
|
|
563
|
-
"array" | "string_array" | "symbol_array" =>
|
|
564
|
+
"array" | "string_array" | "symbol_array" => named_children_of(node, self.context)
|
|
564
565
|
.into_iter()
|
|
565
566
|
.all(|child| self.entirely_literal(child)),
|
|
566
|
-
"hash" =>
|
|
567
|
+
"hash" => named_children_of(node, self.context)
|
|
567
568
|
.into_iter()
|
|
568
569
|
.filter(|child| child.kind_str() == "pair")
|
|
569
570
|
.all(|pair| {
|
|
@@ -127,8 +127,7 @@ fn clause_range(node: Node<'_>) -> Range<usize> {
|
|
|
127
127
|
/// The `resbody` of `x rescue y`, which starts at the keyword and ends with the handler.
|
|
128
128
|
fn modifier_clause(node: Node<'_>, handler: Node<'_>) -> Range<usize> {
|
|
129
129
|
let mut cursor = node.walk();
|
|
130
|
-
let keyword = node
|
|
131
|
-
.children(&mut cursor)
|
|
130
|
+
let keyword = node.children(&mut cursor)
|
|
132
131
|
.find(|child| !child.is_named() && child.kind_str() == "rescue")
|
|
133
132
|
.map_or(node.start_byte(), |child| child.start_byte());
|
|
134
133
|
keyword..handler.end_byte()
|
|
@@ -6,6 +6,7 @@ use crate::diagnostic::Offense;
|
|
|
6
6
|
use crate::rules::RuleContext;
|
|
7
7
|
use crate::rules::node_ext::NodeExt;
|
|
8
8
|
use crate::rules::send_node::{named_children, top_level_constant};
|
|
9
|
+
use crate::rules::send_node::named_children_of;
|
|
9
10
|
|
|
10
11
|
const MSG: &str = "Avoid hard coding large quantities of data in code. \
|
|
11
12
|
Prefer reading the data from an external source.";
|
|
@@ -46,7 +47,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
46
47
|
if !top_level_constant(object, "Set", context) || is_assignment_target(node) {
|
|
47
48
|
continue;
|
|
48
49
|
}
|
|
49
|
-
let children =
|
|
50
|
+
let children = named_children_of(node, context);
|
|
50
51
|
let indices = children.get(1..).unwrap_or_default();
|
|
51
52
|
if fold_pairs(indices).len() >= threshold {
|
|
52
53
|
offenses.push(context.offense(MSG, node.byte_range()));
|
|
@@ -108,6 +108,9 @@ pub(super) enum Order {
|
|
|
108
108
|
|
|
109
109
|
pub(super) struct Walk<'a> {
|
|
110
110
|
source: &'a SourceFile,
|
|
111
|
+
/// The file's node index. The walk asks for a parent on every identifier it meets, and
|
|
112
|
+
/// `Node::parent` walks down from the root each time.
|
|
113
|
+
index: &'a super::super::AstIndex<'a>,
|
|
111
114
|
locals: &'a Locals,
|
|
112
115
|
fragments: &'a Fragments,
|
|
113
116
|
order: Order,
|
|
@@ -122,6 +125,7 @@ impl<'a> Walk<'a> {
|
|
|
122
125
|
) -> Self {
|
|
123
126
|
Self {
|
|
124
127
|
source: context.source,
|
|
128
|
+
index: context.ast_index(),
|
|
125
129
|
locals,
|
|
126
130
|
fragments,
|
|
127
131
|
order,
|
|
@@ -704,9 +708,10 @@ impl<'a> Walk<'a> {
|
|
|
704
708
|
|
|
705
709
|
fn visit_block_argument<F: FnMut(Emit<'a>)>(&self, node: Node<'a>, sink: &mut F) {
|
|
706
710
|
let mut emit = Emit::of(Kind::BlockPass, node);
|
|
707
|
-
emit.iterating =
|
|
708
|
-
.
|
|
709
|
-
.
|
|
711
|
+
emit.iterating = self
|
|
712
|
+
.index
|
|
713
|
+
.parent(node)
|
|
714
|
+
.and_then(|arguments| self.index.parent(arguments))
|
|
710
715
|
.filter(|call| call.kind_str() == "call")
|
|
711
716
|
.and_then(|call| self.iterating_call(call));
|
|
712
717
|
self.around(emit, sink, |walk, sink| walk.children(node, sink));
|
|
@@ -829,7 +834,7 @@ impl<'a> Walk<'a> {
|
|
|
829
834
|
|
|
830
835
|
/// Whether the identifier stands where the parser would have built `(send nil :name)`.
|
|
831
836
|
fn receiverless_call(&self, node: Node<'_>) -> bool {
|
|
832
|
-
is_receiverless_call(node)
|
|
837
|
+
is_receiverless_call(node, self.index)
|
|
833
838
|
&& !self.locals.is_lvar(node)
|
|
834
839
|
&& !is_keyword_literal(self.text(node))
|
|
835
840
|
&& !self.is_implicit_block_parameter(node)
|
|
@@ -841,7 +846,7 @@ impl<'a> Walk<'a> {
|
|
|
841
846
|
if self.text(node) != "it" {
|
|
842
847
|
return false;
|
|
843
848
|
}
|
|
844
|
-
let mut current =
|
|
849
|
+
let mut current = self.index.parent(node);
|
|
845
850
|
while let Some(parent) = current {
|
|
846
851
|
if matches!(parent.kind_str(), "block" | "do_block") {
|
|
847
852
|
return parent.field("parameters").is_none();
|
|
@@ -852,7 +857,7 @@ impl<'a> Walk<'a> {
|
|
|
852
857
|
) {
|
|
853
858
|
return false;
|
|
854
859
|
}
|
|
855
|
-
current =
|
|
860
|
+
current = self.index.parent(parent);
|
|
856
861
|
}
|
|
857
862
|
false
|
|
858
863
|
}
|
|
@@ -1091,14 +1096,14 @@ fn collect_targets<'a>(node: Node<'a>, targets: &mut Vec<Node<'a>>) {
|
|
|
1091
1096
|
|
|
1092
1097
|
/// Whether the identifier stands where the parser would have built `(send nil :name)` rather than
|
|
1093
1098
|
/// a name being declared, written or called on something else.
|
|
1094
|
-
fn is_receiverless_call(node: Node<'_>) -> bool {
|
|
1095
|
-
let Some(parent) =
|
|
1099
|
+
fn is_receiverless_call(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
|
|
1100
|
+
let Some(parent) = index.parent(node) else {
|
|
1096
1101
|
return true;
|
|
1097
1102
|
};
|
|
1098
1103
|
match parent.kind_str() {
|
|
1099
|
-
"call" => field_name(node, parent) != Some("method"),
|
|
1100
|
-
"method" | "singleton_method" => field_name(node, parent) != Some("name"),
|
|
1101
|
-
"assignment" | "operator_assignment" => field_name(node, parent) != Some("left"),
|
|
1104
|
+
"call" => field_name(node, parent, index) != Some("method"),
|
|
1105
|
+
"method" | "singleton_method" => field_name(node, parent, index) != Some("name"),
|
|
1106
|
+
"assignment" | "operator_assignment" => field_name(node, parent, index) != Some("left"),
|
|
1102
1107
|
"left_assignment_list"
|
|
1103
1108
|
| "rest_assignment"
|
|
1104
1109
|
| "destructured_left_assignment"
|
|
@@ -1114,8 +1119,8 @@ fn is_receiverless_call(node: Node<'_>) -> bool {
|
|
|
1114
1119
|
| "keyword_parameter"
|
|
1115
1120
|
| "splat_parameter"
|
|
1116
1121
|
| "hash_splat_parameter"
|
|
1117
|
-
| "block_parameter" => field_name(node, parent) != Some("name"),
|
|
1118
|
-
"for" => field_name(node, parent) != Some("pattern"),
|
|
1122
|
+
| "block_parameter" => field_name(node, parent, index) != Some("name"),
|
|
1123
|
+
"for" => field_name(node, parent, index) != Some("pattern"),
|
|
1119
1124
|
_ => true,
|
|
1120
1125
|
}
|
|
1121
1126
|
}
|