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
data/src/rules/mod.rs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
use std::cell::OnceCell;
|
|
2
2
|
use std::collections::HashMap;
|
|
3
3
|
use std::ops::Range;
|
|
4
|
+
use std::sync::LazyLock;
|
|
4
5
|
|
|
5
6
|
use tree_sitter::Node;
|
|
6
7
|
|
|
7
8
|
use crate::rules::lint::variable_force::Analysis;
|
|
8
9
|
use crate::rules::metrics::fragments::Fragments;
|
|
9
10
|
use crate::rules::metrics::locals::Locals;
|
|
10
|
-
use crate::rules::naming::support::Variables;
|
|
11
11
|
|
|
12
12
|
use crate::config::Config;
|
|
13
13
|
use crate::diagnostic::{Offense, Severity};
|
|
@@ -60,7 +60,7 @@ mod style;
|
|
|
60
60
|
pub(crate) mod support;
|
|
61
61
|
mod visibility;
|
|
62
62
|
|
|
63
|
-
pub(crate) use support::{push_named_children, walk_named};
|
|
63
|
+
pub(crate) use support::{push_named_children, push_named_children_in, walk_named};
|
|
64
64
|
|
|
65
65
|
/// A cop: its qualified name, the severity it reports at by default, and the function that
|
|
66
66
|
/// inspects one file.
|
|
@@ -135,15 +135,14 @@ pub(crate) struct RuleContext<'a> {
|
|
|
135
135
|
/// The context is reused across every cop of a file for this reason; see
|
|
136
136
|
/// [`Self::inspecting_with`].
|
|
137
137
|
analysis: OnceCell<Analysis<'a>>,
|
|
138
|
-
/// The Naming department's own reading of which names are variables, run at most once per
|
|
139
|
-
/// file. Five cops ask for it, and like [`Self::variable_analysis`] it depends on nothing but
|
|
140
|
-
/// the tree and the source.
|
|
141
|
-
variables: OnceCell<Variables>,
|
|
142
138
|
/// The code the grammar swallowed, recovered once per file. The three complexity cops each
|
|
143
139
|
/// used to recover it for themselves.
|
|
144
140
|
fragments: OnceCell<Fragments>,
|
|
145
141
|
/// Which identifiers the Metrics cops read as local variables, replayed once per file.
|
|
146
142
|
metric_locals: OnceCell<Locals>,
|
|
143
|
+
/// The lexer token stream reconstructed from the tree, shared by the Layout and Style cops
|
|
144
|
+
/// that inspect neighbouring tokens rather than syntax nodes.
|
|
145
|
+
layout_tokens: OnceCell<Vec<layout::tokens::Token>>,
|
|
147
146
|
/// The cops the run switches off through the configuration, which is what an `enable` directive
|
|
148
147
|
/// has to undo.
|
|
149
148
|
///
|
|
@@ -185,9 +184,9 @@ impl<'a> RuleContext<'a> {
|
|
|
185
184
|
correcting,
|
|
186
185
|
directive_review: None,
|
|
187
186
|
analysis: OnceCell::new(),
|
|
188
|
-
variables: OnceCell::new(),
|
|
189
187
|
fragments: OnceCell::new(),
|
|
190
188
|
metric_locals: OnceCell::new(),
|
|
189
|
+
layout_tokens: OnceCell::new(),
|
|
191
190
|
disabled_cops: &[],
|
|
192
191
|
}
|
|
193
192
|
}
|
|
@@ -214,25 +213,37 @@ impl<'a> RuleContext<'a> {
|
|
|
214
213
|
|
|
215
214
|
/// RuboCop's `VariableForce` result for this file, computed on the first cop that asks.
|
|
216
215
|
pub(in crate::rules) fn variable_analysis(&self) -> &Analysis<'a> {
|
|
217
|
-
self.analysis
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
pub(in crate::rules) fn variable_roles(&self) -> &Variables {
|
|
223
|
-
self.variables
|
|
224
|
-
.get_or_init(|| Variables::resolve(self.ast.root, self.source))
|
|
216
|
+
self.analysis.get_or_init(|| {
|
|
217
|
+
crate::profile::phase(crate::profile::Phase::Variables, || {
|
|
218
|
+
Analysis::run(self.ast, self.source)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
225
221
|
}
|
|
226
222
|
|
|
227
223
|
/// The code the grammar read as something other than code, recovered once per file.
|
|
228
224
|
pub(in crate::rules) fn fragments(&self) -> &Fragments {
|
|
229
|
-
self.fragments.get_or_init(||
|
|
225
|
+
self.fragments.get_or_init(|| {
|
|
226
|
+
crate::profile::phase(crate::profile::Phase::Fragments, || Fragments::new(self))
|
|
227
|
+
})
|
|
230
228
|
}
|
|
231
229
|
|
|
232
230
|
/// Which identifiers the Metrics cops read as local variables.
|
|
233
231
|
pub(in crate::rules) fn metric_locals(&self) -> &Locals {
|
|
234
|
-
self.metric_locals
|
|
235
|
-
|
|
232
|
+
self.metric_locals.get_or_init(|| {
|
|
233
|
+
crate::profile::phase(crate::profile::Phase::MetricLocals, || {
|
|
234
|
+
Locals::new(self, self.fragments())
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// RuboCop's lexer token stream for this file, reconstructed at most once however many cops
|
|
240
|
+
/// inspect it.
|
|
241
|
+
pub(in crate::rules) fn layout_tokens(&self) -> &[layout::tokens::Token] {
|
|
242
|
+
self.layout_tokens.get_or_init(|| {
|
|
243
|
+
crate::profile::phase(crate::profile::Phase::LayoutTokens, || {
|
|
244
|
+
layout::tokens::build(self)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
236
247
|
}
|
|
237
248
|
}
|
|
238
249
|
|
|
@@ -309,6 +320,38 @@ impl<'a> RuleContext<'a> {
|
|
|
309
320
|
self.ast.root
|
|
310
321
|
}
|
|
311
322
|
|
|
323
|
+
/// The file's node index, for the helpers that answer structural questions -- a parent above
|
|
324
|
+
/// all -- outside a cop's own `check`.
|
|
325
|
+
pub(in crate::rules) fn ast_index(&self) -> &'a AstIndex<'a> {
|
|
326
|
+
self.ast
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/// `Node::children` for a node of this file, answered from the index. See
|
|
330
|
+
/// [`AstIndex::children_of`].
|
|
331
|
+
pub(in crate::rules) fn children<'node>(
|
|
332
|
+
&'node self,
|
|
333
|
+
node: Node<'node>,
|
|
334
|
+
) -> Option<Children<'node>> {
|
|
335
|
+
self.ast.children_of(node)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// `Node::named_children` for a node of this file, answered from the index. See
|
|
339
|
+
/// [`AstIndex::named_children_of`].
|
|
340
|
+
pub(in crate::rules) fn named_children<'node>(
|
|
341
|
+
&'node self,
|
|
342
|
+
node: Node<'node>,
|
|
343
|
+
) -> Option<&'node [Node<'node>]> {
|
|
344
|
+
self.ast.named_children_of(node)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// Every named node of `node`'s subtree, `node` first. See [`AstIndex::named_descendants`].
|
|
348
|
+
pub(in crate::rules) fn named_descendants<'node>(
|
|
349
|
+
&'node self,
|
|
350
|
+
node: Node<'node>,
|
|
351
|
+
) -> Option<&'node [Node<'node>]> {
|
|
352
|
+
self.ast.named_descendants(node)
|
|
353
|
+
}
|
|
354
|
+
|
|
312
355
|
pub fn nodes(&self) -> impl Iterator<Item = Node<'a>> + '_ {
|
|
313
356
|
self.ast.nodes.iter().copied()
|
|
314
357
|
}
|
|
@@ -325,13 +368,30 @@ impl<'a> RuleContext<'a> {
|
|
|
325
368
|
/// The named nodes of any of `kinds`, in source order. The kinds are indexed separately, so
|
|
326
369
|
/// their positions have to be merged to put the nodes back in the order a cop that scans the
|
|
327
370
|
/// whole file would have seen them in.
|
|
371
|
+
///
|
|
372
|
+
/// A single kind is by far the common case and is handed straight through: the per-kind lists
|
|
373
|
+
/// are already in source order, so there is nothing to merge and **the list is borrowed**.
|
|
374
|
+
/// Copying it out cost an allocation and a memcpy of the whole list on every cop of every
|
|
375
|
+
/// file, and the list is thousands of entries wide for a kind as ordinary as `call`.
|
|
328
376
|
pub fn nodes_of_any(&self, kinds: &[&str]) -> impl Iterator<Item = Node<'a>> + '_ {
|
|
329
|
-
let
|
|
330
|
-
.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
377
|
+
let positions: std::borrow::Cow<'_, [u32]> = match kinds {
|
|
378
|
+
[only] => std::borrow::Cow::Borrowed(self.ast.slice_of_kind(only)),
|
|
379
|
+
_ => {
|
|
380
|
+
// One allocation of the right size. `collect` from a `flat_map` cannot see the
|
|
381
|
+
// total ahead of time and grows the vector as it goes.
|
|
382
|
+
let total: usize = kinds
|
|
383
|
+
.iter()
|
|
384
|
+
.map(|kind| self.ast.slice_of_kind(kind).len())
|
|
385
|
+
.sum();
|
|
386
|
+
let mut indices = Vec::with_capacity(total);
|
|
387
|
+
for kind in kinds {
|
|
388
|
+
indices.extend_from_slice(self.ast.slice_of_kind(kind));
|
|
389
|
+
}
|
|
390
|
+
indices.sort_unstable();
|
|
391
|
+
std::borrow::Cow::Owned(indices)
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
(0..positions.len()).map(move |at| self.ast.named_node(positions[at]))
|
|
335
395
|
}
|
|
336
396
|
|
|
337
397
|
pub fn protected_ranges(&self) -> &[Range<usize>] {
|
|
@@ -372,45 +432,184 @@ const PROTECTED_LITERAL_KINDS: &[&str] = &[
|
|
|
372
432
|
"bare_string",
|
|
373
433
|
];
|
|
374
434
|
|
|
435
|
+
/// FNV-1a over the node kind names.
|
|
436
|
+
///
|
|
437
|
+
/// The keys are tree-sitter's kind strings -- short, ASCII, and looked up once per cop per file,
|
|
438
|
+
/// which is hundreds of thousands of times in a run. SipHash's resistance to collision attacks
|
|
439
|
+
/// buys nothing against a fixed set of names the grammar chose, and its setup costs more than the
|
|
440
|
+
/// hash of a ten-character string.
|
|
441
|
+
#[derive(Default)]
|
|
442
|
+
pub(crate) struct KindHasher(u64);
|
|
443
|
+
|
|
444
|
+
impl std::hash::Hasher for KindHasher {
|
|
445
|
+
fn finish(&self) -> u64 {
|
|
446
|
+
self.0
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
fn write(&mut self, bytes: &[u8]) {
|
|
450
|
+
const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
|
|
451
|
+
const PRIME: u64 = 0x0000_0100_0000_01b3;
|
|
452
|
+
let mut hash = if self.0 == 0 { OFFSET } else { self.0 };
|
|
453
|
+
for byte in bytes {
|
|
454
|
+
hash ^= u64::from(*byte);
|
|
455
|
+
hash = hash.wrapping_mul(PRIME);
|
|
456
|
+
}
|
|
457
|
+
self.0 = hash;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
type KindMap = HashMap<&'static str, Vec<u32>, std::hash::BuildHasherDefault<KindHasher>>;
|
|
462
|
+
|
|
463
|
+
/// The hash for a node id, which is the address tree-sitter stored the subtree at.
|
|
464
|
+
///
|
|
465
|
+
/// The keys are pointers into one arena: already well spread above the alignment, and never
|
|
466
|
+
/// chosen by anything but the parser. Multiplying by the 64-bit golden ratio moves the low bits
|
|
467
|
+
/// that alignment fixes into the top of the word, which is where the table reads from -- SipHash's
|
|
468
|
+
/// four rounds buy nothing here and are asked for on every node of every file.
|
|
469
|
+
#[derive(Default)]
|
|
470
|
+
pub(crate) struct IdHasher(u64);
|
|
471
|
+
|
|
472
|
+
impl std::hash::Hasher for IdHasher {
|
|
473
|
+
fn finish(&self) -> u64 {
|
|
474
|
+
self.0
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
fn write(&mut self, bytes: &[u8]) {
|
|
478
|
+
for byte in bytes {
|
|
479
|
+
self.0 = (self.0 ^ u64::from(*byte)).wrapping_mul(0x9e37_79b9_7f4a_7c15);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
fn write_usize(&mut self, value: usize) {
|
|
484
|
+
self.0 = (value as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
type IdMap = HashMap<usize, u32, std::hash::BuildHasherDefault<IdHasher>>;
|
|
489
|
+
|
|
490
|
+
/// The hasher every id-keyed table should be built with. See [`IdHasher`].
|
|
491
|
+
pub(crate) type IdHash = std::hash::BuildHasherDefault<IdHasher>;
|
|
492
|
+
|
|
493
|
+
/// A set of node ids.
|
|
494
|
+
pub(crate) type IdSet = std::collections::HashSet<usize, IdHash>;
|
|
495
|
+
|
|
496
|
+
/// A map from a node id.
|
|
497
|
+
pub(crate) type IdKeyed<V> = HashMap<usize, V, IdHash>;
|
|
498
|
+
|
|
375
499
|
pub(crate) struct AstIndex<'tree> {
|
|
376
500
|
root: Node<'tree>,
|
|
377
501
|
nodes: Vec<Node<'tree>>,
|
|
378
502
|
named_nodes: Vec<Node<'tree>>,
|
|
379
503
|
/// Positions in `named_nodes` grouped by node kind, each list in source order. Indices rather
|
|
380
504
|
/// than nodes because a `Node` is eight times the size of the `u32` that finds it.
|
|
381
|
-
by_kind:
|
|
505
|
+
by_kind: KindMap,
|
|
382
506
|
protected_ranges: Vec<Range<usize>>,
|
|
383
507
|
heredoc_ranges: Vec<Range<usize>>,
|
|
384
508
|
comment_ranges: Vec<Range<usize>>,
|
|
385
|
-
///
|
|
386
|
-
///
|
|
509
|
+
/// Where each node sits in `nodes`, keyed by the node's own id. Both structural answers below
|
|
510
|
+
/// start here.
|
|
511
|
+
positions: IdMap,
|
|
512
|
+
/// Each node's parent, as its position in `nodes`. [`NO_PARENT`] stands for the root.
|
|
387
513
|
///
|
|
388
514
|
/// `Node::parent` walks down from the root of the tree comparing byte ranges, which costs a
|
|
389
515
|
/// pass over the children of every ancestor -- 43% of a run over RuboCop's own tree once the
|
|
390
516
|
/// cheaper accessors were dealt with. The walk that builds this index already knows every
|
|
391
517
|
/// node's parent, so recording it turns the question into one hash lookup.
|
|
518
|
+
parent_of: Vec<u32>,
|
|
519
|
+
/// Every node's named children, laid end to end, with `child_start[position]` naming where a
|
|
520
|
+
/// node's run begins and `child_start[position + 1]` where it ends.
|
|
521
|
+
///
|
|
522
|
+
/// `Node::named_children` opens a tree cursor and collects into a fresh `Vec` on every call.
|
|
523
|
+
/// Both showed at the top of a sampling profile: the cursor iteration was the largest single
|
|
524
|
+
/// cost of a run and the collect the largest allocation. The walk that fills `nodes` already
|
|
525
|
+
/// visits every child, so the list is recorded rather than rebuilt.
|
|
526
|
+
named_children: Vec<Node<'tree>>,
|
|
527
|
+
child_start: Vec<u32>,
|
|
528
|
+
/// Where each node sits in `named_nodes`, or [`NOT_NAMED`] for one that is not named.
|
|
529
|
+
named_index: Vec<u32>,
|
|
530
|
+
/// The field each node fills in its parent, as the grammar's field id, or 0 for a node that
|
|
531
|
+
/// fills none.
|
|
392
532
|
///
|
|
393
|
-
///
|
|
394
|
-
///
|
|
395
|
-
|
|
533
|
+
/// Answering "which field is this child" by walking the parent's children with a cursor costs
|
|
534
|
+
/// one walk per question, and the cops that classify identifiers ask on every identifier of
|
|
535
|
+
/// every file. The walk that builds the index passes each node once with a cursor that already
|
|
536
|
+
/// knows the answer.
|
|
537
|
+
field_of: Vec<u32>,
|
|
538
|
+
/// How many nodes each node's subtree holds, itself included. `nodes` is in pre-order, so a
|
|
539
|
+
/// node's own children are found by stepping over one subtree at a time from the node after
|
|
540
|
+
/// it -- which is what lets [`Self::children_of`] answer without a tree cursor.
|
|
541
|
+
subtree_len: Vec<u32>,
|
|
542
|
+
/// How many named nodes each node's subtree holds, itself included.
|
|
543
|
+
///
|
|
544
|
+
/// `named_nodes` is in pre-order, so a node's descendants are the run that begins where the
|
|
545
|
+
/// node itself sits and is this long -- which is what turns "every named node below this one"
|
|
546
|
+
/// from a stack-driven walk into a slice.
|
|
547
|
+
named_subtree: Vec<u32>,
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/// One node's children, walked through the index's pre-order table. See
|
|
551
|
+
/// [`AstIndex::children_of`].
|
|
552
|
+
pub(in crate::rules) struct Children<'a> {
|
|
553
|
+
nodes: &'a [Node<'a>],
|
|
554
|
+
subtree_len: &'a [u32],
|
|
555
|
+
next: usize,
|
|
556
|
+
end: usize,
|
|
396
557
|
}
|
|
397
558
|
|
|
398
|
-
|
|
559
|
+
impl<'a> Iterator for Children<'a> {
|
|
560
|
+
type Item = Node<'a>;
|
|
561
|
+
|
|
562
|
+
fn next(&mut self) -> Option<Node<'a>> {
|
|
563
|
+
if self.next >= self.end {
|
|
564
|
+
return None;
|
|
565
|
+
}
|
|
566
|
+
let node = self.nodes[self.next];
|
|
567
|
+
self.next += self.subtree_len[self.next] as usize;
|
|
568
|
+
Some(node)
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/// Every field name the grammar has, by field id, so a recorded id can be turned back into the
|
|
573
|
+
/// `&'static str` the cops compare against.
|
|
574
|
+
static FIELD_NAMES: LazyLock<Vec<Option<&'static str>>> = LazyLock::new(|| {
|
|
575
|
+
let language = node_ext::language();
|
|
576
|
+
(0..=language.field_count() as u16)
|
|
577
|
+
.map(|id| language.field_name_for_id(id))
|
|
578
|
+
.collect()
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
/// The value [`AstIndex::parent_of`] carries for the root.
|
|
399
582
|
const NO_PARENT: u32 = u32::MAX;
|
|
400
583
|
|
|
584
|
+
/// The value [`AstIndex::named_index`] carries for a node that is not named.
|
|
585
|
+
const NOT_NAMED: u32 = u32::MAX;
|
|
586
|
+
|
|
401
587
|
impl<'tree> AstIndex<'tree> {
|
|
402
588
|
pub fn new(root: Node<'tree>) -> Self {
|
|
589
|
+
// The tree knows how many nodes it holds, so every per-node table is allocated once at the
|
|
590
|
+
// right size. Growing them by doubling costs a dozen reallocations and copies per file,
|
|
591
|
+
// and eight workers doing that at once is contention on the allocator rather than work.
|
|
592
|
+
let count = root.descendant_count();
|
|
403
593
|
let mut index = Self {
|
|
404
594
|
root,
|
|
405
|
-
nodes: Vec::
|
|
406
|
-
named_nodes: Vec::
|
|
407
|
-
by_kind:
|
|
595
|
+
nodes: Vec::with_capacity(count),
|
|
596
|
+
named_nodes: Vec::with_capacity(count),
|
|
597
|
+
by_kind: KindMap::default(),
|
|
408
598
|
protected_ranges: Vec::new(),
|
|
409
599
|
heredoc_ranges: Vec::new(),
|
|
410
600
|
comment_ranges: Vec::new(),
|
|
411
|
-
|
|
601
|
+
positions: IdMap::with_capacity_and_hasher(count, Default::default()),
|
|
602
|
+
parent_of: Vec::with_capacity(count),
|
|
603
|
+
named_children: Vec::new(),
|
|
604
|
+
child_start: Vec::new(),
|
|
605
|
+
named_index: Vec::with_capacity(count),
|
|
606
|
+
field_of: Vec::with_capacity(count),
|
|
607
|
+
subtree_len: Vec::new(),
|
|
608
|
+
named_subtree: Vec::new(),
|
|
412
609
|
};
|
|
413
610
|
index.collect(root);
|
|
611
|
+
index.index_children();
|
|
612
|
+
index.index_subtrees();
|
|
414
613
|
index.protected_ranges.sort_by_key(|range| range.start);
|
|
415
614
|
merge_touching_ranges(&mut index.protected_ranges);
|
|
416
615
|
index.heredoc_ranges.sort_by_key(|range| range.start);
|
|
@@ -422,11 +621,12 @@ impl<'tree> AstIndex<'tree> {
|
|
|
422
621
|
}
|
|
423
622
|
|
|
424
623
|
fn of_kind(&self, kind: &str) -> impl Iterator<Item = u32> + '_ {
|
|
425
|
-
self.
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
624
|
+
self.slice_of_kind(kind).iter().copied()
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/// The positions for one kind, in source order.
|
|
628
|
+
fn slice_of_kind(&self, kind: &str) -> &[u32] {
|
|
629
|
+
self.by_kind.get(kind).map_or(&[][..], Vec::as_slice)
|
|
430
630
|
}
|
|
431
631
|
|
|
432
632
|
fn named_node(&self, index: u32) -> Node<'tree> {
|
|
@@ -439,44 +639,164 @@ impl<'tree> AstIndex<'tree> {
|
|
|
439
639
|
/// `Metrics`' recovered fragments are -- is asked of the parser itself, so the answer is the
|
|
440
640
|
/// one `Node::parent` would have given whatever tree the node came from.
|
|
441
641
|
fn parent<'node>(&'node self, node: Node<'node>) -> Option<Node<'node>> {
|
|
442
|
-
match self
|
|
443
|
-
.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
Some(&NO_PARENT) => None,
|
|
448
|
-
Some(&index) => Some(self.nodes[index as usize]),
|
|
642
|
+
match self.positions.get(&node.id()) {
|
|
643
|
+
Some(&position) => match self.parent_of[position as usize] {
|
|
644
|
+
NO_PARENT => None,
|
|
645
|
+
index => Some(self.nodes[index as usize]),
|
|
646
|
+
},
|
|
449
647
|
None => node.parent(),
|
|
450
648
|
}
|
|
451
649
|
}
|
|
452
650
|
|
|
453
|
-
///
|
|
454
|
-
///
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
651
|
+
/// The named nodes of one kind, in source order -- the same list [`RuleContext::nodes_of`]
|
|
652
|
+
/// hands a cop, for the helpers that hold an index rather than a context.
|
|
653
|
+
pub(in crate::rules) fn nodes_of_kind<'a>(
|
|
654
|
+
&'a self,
|
|
655
|
+
kind: &str,
|
|
656
|
+
) -> impl Iterator<Item = Node<'tree>> + 'a {
|
|
657
|
+
self.of_kind(kind).map(|index| self.named_node(index))
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/// The field `node` fills in its parent, as `TreeCursor::field_name` reports it, or `None`
|
|
661
|
+
/// for a node that fills none.
|
|
662
|
+
///
|
|
663
|
+
/// A node the index does not know answers `None` as well, so a caller that must tell the two
|
|
664
|
+
/// apart asks [`Self::knows`] first.
|
|
665
|
+
pub(in crate::rules) fn field_name_of(&self, node: Node<'_>) -> Option<&'static str> {
|
|
666
|
+
let position = *self.positions.get(&node.id())? as usize;
|
|
667
|
+
match self.field_of[position] {
|
|
668
|
+
0 => None,
|
|
669
|
+
id => FIELD_NAMES.get(id as usize).copied().flatten(),
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/// Whether the node belongs to the tree this index was built from.
|
|
674
|
+
pub(in crate::rules) fn knows(&self, node: Node<'_>) -> bool {
|
|
675
|
+
self.positions.contains_key(&node.id())
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/// Every child of `node`, named or not, in the order the parser wrote them.
|
|
679
|
+
///
|
|
680
|
+
/// `Node::children` opens a tree cursor for each node it is asked about, and a sampling
|
|
681
|
+
/// profile of a run over RuboCop's own tree put that cursor's iteration first among every
|
|
682
|
+
/// cost. The pre-order table already holds the children: the first sits right after the node,
|
|
683
|
+
/// and each next one a whole subtree further on.
|
|
684
|
+
pub(in crate::rules) fn children_of<'node>(
|
|
685
|
+
&'node self,
|
|
686
|
+
node: Node<'node>,
|
|
687
|
+
) -> Option<Children<'node>> {
|
|
688
|
+
let position = *self.positions.get(&node.id())? as usize;
|
|
689
|
+
Some(Children {
|
|
690
|
+
nodes: &self.nodes,
|
|
691
|
+
subtree_len: &self.subtree_len,
|
|
692
|
+
next: position + 1,
|
|
693
|
+
end: position + self.subtree_len[position] as usize,
|
|
694
|
+
})
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/// Every named node of `node`'s subtree, `node` itself first and the rest in depth-first
|
|
698
|
+
/// pre-order -- the order a stack-driven walk produces.
|
|
699
|
+
///
|
|
700
|
+
/// `named_nodes` is filled in that order, so the answer is a run of it rather than a walk.
|
|
701
|
+
pub(in crate::rules) fn named_descendants<'node>(
|
|
702
|
+
&'node self,
|
|
703
|
+
node: Node<'node>,
|
|
704
|
+
) -> Option<&'node [Node<'node>]> {
|
|
705
|
+
let position = *self.positions.get(&node.id())? as usize;
|
|
706
|
+
let start = match self.named_index[position] {
|
|
707
|
+
NOT_NAMED => return None,
|
|
708
|
+
index => index as usize,
|
|
709
|
+
};
|
|
710
|
+
Some(&self.named_nodes[start..start + self.named_subtree[position] as usize])
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/// The named children of a node of this file's tree, as they were recorded when the index was
|
|
714
|
+
/// built. A node the index does not know is walked with a cursor, which is what
|
|
715
|
+
/// `Node::named_children` would have done for every node.
|
|
716
|
+
pub(in crate::rules) fn named_children_of<'node>(
|
|
717
|
+
&'node self,
|
|
718
|
+
node: Node<'node>,
|
|
719
|
+
) -> Option<&'node [Node<'node>]> {
|
|
720
|
+
let position = *self.positions.get(&node.id())? as usize;
|
|
721
|
+
let start = self.child_start[position] as usize;
|
|
722
|
+
let end = self.child_start[position + 1] as usize;
|
|
723
|
+
Some(&self.named_children[start..end])
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/// [`Self::parent`] for a node of the tree this index was built from, answered without tying
|
|
727
|
+
/// the result to the borrow of the index.
|
|
728
|
+
///
|
|
729
|
+
/// A helper that hands the parent back to its caller, and the variable force, which holds
|
|
730
|
+
/// nodes for as long as the file is inspected, both need the tree's own lifetime rather than
|
|
731
|
+
/// the shorter one `parent` gives.
|
|
732
|
+
/// The tree this index was built from.
|
|
733
|
+
pub(in crate::rules) fn root_node(&self) -> Node<'tree> {
|
|
734
|
+
self.root
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
pub(in crate::rules) fn parent_in_tree(&self, node: Node<'tree>) -> Option<Node<'tree>> {
|
|
738
|
+
match self.positions.get(&node.id()) {
|
|
739
|
+
Some(&position) => match self.parent_of[position as usize] {
|
|
740
|
+
NO_PARENT => None,
|
|
741
|
+
index => Some(self.nodes[index as usize]),
|
|
742
|
+
},
|
|
743
|
+
None => node.parent(),
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/// Groups the named children of every node into one run each, from the parents the walk
|
|
748
|
+
/// recorded. A counting pass and a placing pass, so no node's list is grown as it fills.
|
|
749
|
+
fn index_children(&mut self) {
|
|
750
|
+
let count = self.nodes.len();
|
|
751
|
+
// One slot past the end, so a node's run is `child_start[i]..child_start[i + 1]` without
|
|
752
|
+
// a bounds check on the last node.
|
|
753
|
+
self.child_start = vec![0u32; count + 1];
|
|
754
|
+
for position in 0..count {
|
|
755
|
+
if self.named_index[position] == NOT_NAMED {
|
|
470
756
|
continue;
|
|
471
757
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
758
|
+
let parent = self.parent_of[position];
|
|
759
|
+
if parent == NO_PARENT {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
self.child_start[parent as usize + 1] += 1;
|
|
763
|
+
}
|
|
764
|
+
for index in 1..=count {
|
|
765
|
+
self.child_start[index] += self.child_start[index - 1];
|
|
766
|
+
}
|
|
767
|
+
// `child_start` now holds each node's end; filling backwards from it leaves it holding
|
|
768
|
+
// the start again, which is the usual way to build a compressed adjacency list.
|
|
769
|
+
let total = self.child_start[count] as usize;
|
|
770
|
+
self.named_children = vec![self.root; total];
|
|
771
|
+
let mut cursor = self.child_start.clone();
|
|
772
|
+
for position in 0..count {
|
|
773
|
+
if self.named_index[position] == NOT_NAMED {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
let parent = self.parent_of[position];
|
|
777
|
+
if parent == NO_PARENT {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
let slot = &mut cursor[parent as usize];
|
|
781
|
+
self.named_children[*slot as usize] = self.nodes[position];
|
|
782
|
+
*slot += 1;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/// Counts the named nodes of every subtree. A node always sits before its children in
|
|
787
|
+
/// `nodes`, so one pass from the end folds each count into its parent's.
|
|
788
|
+
fn index_subtrees(&mut self) {
|
|
789
|
+
self.named_subtree = self
|
|
790
|
+
.named_index
|
|
791
|
+
.iter()
|
|
792
|
+
.map(|index| u32::from(*index != NOT_NAMED))
|
|
793
|
+
.collect();
|
|
794
|
+
self.subtree_len = vec![1u32; self.nodes.len()];
|
|
795
|
+
for position in (1..self.nodes.len()).rev() {
|
|
796
|
+
let parent = self.parent_of[position];
|
|
797
|
+
if parent != NO_PARENT {
|
|
798
|
+
self.named_subtree[parent as usize] += self.named_subtree[position];
|
|
799
|
+
self.subtree_len[parent as usize] += self.subtree_len[position];
|
|
480
800
|
}
|
|
481
801
|
}
|
|
482
802
|
}
|
|
@@ -486,9 +806,17 @@ impl<'tree> AstIndex<'tree> {
|
|
|
486
806
|
/// walk aborts the whole process on deeply nested input.
|
|
487
807
|
fn collect(&mut self, root: Node<'tree>) {
|
|
488
808
|
let mut cursor = root.walk();
|
|
809
|
+
let mut ancestors: Vec<u32> = Vec::new();
|
|
489
810
|
loop {
|
|
490
|
-
self.
|
|
811
|
+
let here = self.nodes.len() as u32;
|
|
812
|
+
let field = cursor.field_id().map_or(0, std::num::NonZeroU16::get);
|
|
813
|
+
self.visit(
|
|
814
|
+
cursor.node(),
|
|
815
|
+
ancestors.last().copied().unwrap_or(NO_PARENT),
|
|
816
|
+
field,
|
|
817
|
+
);
|
|
491
818
|
if cursor.goto_first_child() {
|
|
819
|
+
ancestors.push(here);
|
|
492
820
|
continue;
|
|
493
821
|
}
|
|
494
822
|
loop {
|
|
@@ -498,26 +826,38 @@ impl<'tree> AstIndex<'tree> {
|
|
|
498
826
|
if !cursor.goto_parent() {
|
|
499
827
|
return;
|
|
500
828
|
}
|
|
829
|
+
ancestors.pop();
|
|
501
830
|
}
|
|
502
831
|
}
|
|
503
832
|
}
|
|
504
833
|
|
|
505
|
-
fn visit(&mut self, node: Node<'tree
|
|
834
|
+
fn visit(&mut self, node: Node<'tree>, parent: u32, field: u16) {
|
|
835
|
+
// Read once. Each call goes through the C API for the node's symbol, and this used to ask
|
|
836
|
+
// four times for every node of every file.
|
|
837
|
+
let kind = node.kind_str();
|
|
838
|
+
let named = node.is_named();
|
|
839
|
+
self.positions.insert(node.id(), self.nodes.len() as u32);
|
|
840
|
+
self.parent_of.push(parent);
|
|
841
|
+
self.field_of.push(u32::from(field));
|
|
842
|
+
self.named_index.push(match named {
|
|
843
|
+
true => self.named_nodes.len() as u32,
|
|
844
|
+
false => NOT_NAMED,
|
|
845
|
+
});
|
|
506
846
|
self.nodes.push(node);
|
|
507
|
-
if
|
|
847
|
+
if named {
|
|
508
848
|
// A file with more than u32::MAX named nodes would need tens of gigabytes of source;
|
|
509
849
|
// the cast cannot lose information for anything a parser will accept.
|
|
510
850
|
let index = self.named_nodes.len() as u32;
|
|
511
851
|
self.named_nodes.push(node);
|
|
512
|
-
self.by_kind.entry(
|
|
852
|
+
self.by_kind.entry(kind).or_default().push(index);
|
|
513
853
|
}
|
|
514
|
-
if PROTECTED_LITERAL_KINDS.contains(&
|
|
854
|
+
if PROTECTED_LITERAL_KINDS.contains(&kind) {
|
|
515
855
|
self.protected_ranges.push(node.byte_range());
|
|
516
856
|
}
|
|
517
|
-
if
|
|
857
|
+
if kind == "heredoc_body" {
|
|
518
858
|
self.heredoc_ranges.push(node.byte_range());
|
|
519
859
|
}
|
|
520
|
-
if
|
|
860
|
+
if kind == "comment" && !inside_literal_text(node) {
|
|
521
861
|
self.comment_ranges.push(node.byte_range());
|
|
522
862
|
}
|
|
523
863
|
}
|
|
@@ -577,8 +917,9 @@ fn merge_touching_ranges(ranges: &mut Vec<Range<usize>>) {
|
|
|
577
917
|
mod tests {
|
|
578
918
|
use std::collections::HashSet;
|
|
579
919
|
|
|
580
|
-
use super::{merge_touching_ranges, rule_names, rules};
|
|
920
|
+
use super::{AstIndex, merge_touching_ranges, rule_names, rules};
|
|
581
921
|
use crate::config::Config;
|
|
922
|
+
use crate::rules::node_ext::NodeExt;
|
|
582
923
|
use crate::source::is_protected;
|
|
583
924
|
|
|
584
925
|
#[test]
|
|
@@ -636,6 +977,110 @@ mod tests {
|
|
|
636
977
|
}
|
|
637
978
|
}
|
|
638
979
|
|
|
980
|
+
/// The index answers a parent and a child list from its own arrays rather than from the
|
|
981
|
+
/// parser, so the two have to agree for every node of a real file -- a cop reaching for either
|
|
982
|
+
/// would otherwise see a tree the grammar never built.
|
|
983
|
+
#[test]
|
|
984
|
+
fn the_index_answers_what_the_parser_answers() {
|
|
985
|
+
let source = "# frozen_string_literal: true\n class Foo < Bar\n def baz(a = 1, *rest, key:, &block)\n @x ||= a.map { |v| \"#{v}-#{rest.first}\" }\n text = <<~DOC\n hello #{key}\n DOC\n [1, 2].each_with_object({}) { |n, memo| memo[n] = n }\n rescue StandardError => error\n raise error\n ensure\n puts text\n end\n end\n";
|
|
986
|
+
let mut parser = tree_sitter::Parser::new();
|
|
987
|
+
parser
|
|
988
|
+
.set_language(&tree_sitter_ruby::LANGUAGE.into())
|
|
989
|
+
.expect("the Ruby grammar loads");
|
|
990
|
+
let tree = parser.parse(source, None).expect("the source parses");
|
|
991
|
+
let index = AstIndex::new(tree.root_node());
|
|
992
|
+
|
|
993
|
+
let mut seen = 0;
|
|
994
|
+
for node in &index.nodes {
|
|
995
|
+
let mut cursor = node.walk();
|
|
996
|
+
let expected: Vec<tree_sitter::Node<'_>> = node.named_children(&mut cursor).collect();
|
|
997
|
+
let recorded = index
|
|
998
|
+
.named_children_of(*node)
|
|
999
|
+
.expect("every node of this tree is indexed");
|
|
1000
|
+
let mut all = node.walk();
|
|
1001
|
+
let all_expected: Vec<tree_sitter::Node<'_>> = node.children(&mut all).collect();
|
|
1002
|
+
let all_recorded: Vec<tree_sitter::Node<'_>> = index
|
|
1003
|
+
.children_of(*node)
|
|
1004
|
+
.expect("every node of this tree is indexed")
|
|
1005
|
+
.collect();
|
|
1006
|
+
assert_eq!(
|
|
1007
|
+
all_recorded,
|
|
1008
|
+
all_expected,
|
|
1009
|
+
"{} reported different children",
|
|
1010
|
+
node.kind_str()
|
|
1011
|
+
);
|
|
1012
|
+
assert_eq!(
|
|
1013
|
+
recorded,
|
|
1014
|
+
expected,
|
|
1015
|
+
"{} reported different named children",
|
|
1016
|
+
node.kind_str()
|
|
1017
|
+
);
|
|
1018
|
+
assert_eq!(
|
|
1019
|
+
index.parent_in_tree(*node).map(|found| found.id()),
|
|
1020
|
+
node.parent().map(|found| found.id()),
|
|
1021
|
+
"{} reported a different parent",
|
|
1022
|
+
node.kind_str()
|
|
1023
|
+
);
|
|
1024
|
+
// The field a node fills, as a cursor sitting on it would report.
|
|
1025
|
+
let by_cursor = index.parent_in_tree(*node).and_then(|parent| {
|
|
1026
|
+
let mut walk = parent.walk();
|
|
1027
|
+
walk.goto_first_child().then_some(()).and_then(|()| {
|
|
1028
|
+
loop {
|
|
1029
|
+
if walk.node().id() == node.id() {
|
|
1030
|
+
return walk.field_name();
|
|
1031
|
+
}
|
|
1032
|
+
if !walk.goto_next_sibling() {
|
|
1033
|
+
return None;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
})
|
|
1037
|
+
});
|
|
1038
|
+
assert_eq!(
|
|
1039
|
+
index.field_name_of(*node),
|
|
1040
|
+
by_cursor,
|
|
1041
|
+
"{} reported a different field",
|
|
1042
|
+
node.kind_str()
|
|
1043
|
+
);
|
|
1044
|
+
seen += 1;
|
|
1045
|
+
}
|
|
1046
|
+
assert!(
|
|
1047
|
+
seen > 100,
|
|
1048
|
+
"the sample file should exercise more than {seen} nodes"
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/// A node from another tree -- the extra parses `Metrics` makes to recover what the grammar
|
|
1053
|
+
/// swallowed -- is not in the index, and both accessors have to fall back to the parser rather
|
|
1054
|
+
/// than answer for a node they never saw.
|
|
1055
|
+
#[test]
|
|
1056
|
+
fn a_node_from_another_tree_falls_back_to_the_parser() {
|
|
1057
|
+
let mut parser = tree_sitter::Parser::new();
|
|
1058
|
+
parser
|
|
1059
|
+
.set_language(&tree_sitter_ruby::LANGUAGE.into())
|
|
1060
|
+
.expect("the Ruby grammar loads");
|
|
1061
|
+
let indexed = parser.parse("foo(1)\n", None).expect("the source parses");
|
|
1062
|
+
let index = AstIndex::new(indexed.root_node());
|
|
1063
|
+
|
|
1064
|
+
let other = parser
|
|
1065
|
+
.parse("bar(2, 3)\n", None)
|
|
1066
|
+
.expect("the source parses");
|
|
1067
|
+
let call = other
|
|
1068
|
+
.root_node()
|
|
1069
|
+
.named_child(0)
|
|
1070
|
+
.expect("the program has a statement");
|
|
1071
|
+
assert!(index.named_children_of(call).is_none());
|
|
1072
|
+
let mut cursor = call.walk();
|
|
1073
|
+
let expected: Vec<tree_sitter::Node<'_>> = call.named_children(&mut cursor).collect();
|
|
1074
|
+
assert_eq!(
|
|
1075
|
+
crate::rules::send_node::named_children_in(call, &index).into_owned(),
|
|
1076
|
+
expected
|
|
1077
|
+
);
|
|
1078
|
+
assert_eq!(
|
|
1079
|
+
index.parent_in_tree(call).map(|found| found.id()),
|
|
1080
|
+
call.parent().map(|found| found.id())
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
639
1084
|
/// The registry is a static built from the department tables, so iteration order cannot vary
|
|
640
1085
|
/// between runs; autocorrect ordering and `--debug` output both rely on that.
|
|
641
1086
|
#[test]
|