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/naming/support.rs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
//! Name spelling and variable resolution shared by the cops that enforce `EnforcedStyle`.
|
|
2
2
|
|
|
3
|
-
use std::collections::{HashMap, HashSet};
|
|
4
3
|
use std::ops::Range;
|
|
5
4
|
use std::sync::LazyLock;
|
|
6
5
|
|
|
@@ -9,6 +8,8 @@ use tree_sitter::Node;
|
|
|
9
8
|
|
|
10
9
|
use crate::rules::RuleContext;
|
|
11
10
|
use crate::rules::node_ext::NodeExt;
|
|
11
|
+
use crate::rules::send_node::named_children_iter;
|
|
12
|
+
use crate::rules::send_node::named_children_of;
|
|
12
13
|
use crate::rules::support::spurious_assignment_list;
|
|
13
14
|
use crate::source::SourceFile;
|
|
14
15
|
|
|
@@ -30,294 +31,11 @@ pub(super) fn valid_name(name: &str, style: &str) -> bool {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
/// Which identifiers in one file are variables, and in what role.
|
|
34
|
-
///
|
|
35
|
-
/// Ruby tells a local variable read apart from a receiverless method call by whether the parser
|
|
36
|
-
/// has already seen the name bound in the enclosing scope, and two cops here turn on that
|
|
37
|
-
/// distinction: `Naming/VariableName` reports every `lvar` through `on_lvar`, and
|
|
38
|
-
/// `Naming/ConstantName` excuses `CONST = some_method` while reporting `CONST = some_local`.
|
|
39
|
-
/// tree-sitter spells both as `identifier`, so the scopes have to be replayed to separate them.
|
|
40
|
-
pub(in crate::rules) struct Variables {
|
|
41
|
-
roles: HashMap<usize, Role>,
|
|
42
|
-
/// The method names of receiverless calls that also name a local variable in scope.
|
|
43
|
-
///
|
|
44
|
-
/// The grammar reads `collection [0]` as a call handed an array while Ruby reads it as an index
|
|
45
|
-
/// on the local variable, and only the scope replay can tell the two apart. Nothing upstream
|
|
46
|
-
/// corresponds to this position, so it is kept apart from the roles a variable handler sees.
|
|
47
|
-
local_calls: HashSet<usize>,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
impl Variables {
|
|
51
|
-
pub(in crate::rules) fn resolve<'a>(root: Node<'_>, source: &'a SourceFile) -> Self {
|
|
52
|
-
let mut roles = HashMap::new();
|
|
53
|
-
let mut local_calls = HashSet::new();
|
|
54
|
-
let mut scopes: Vec<Scope<'a>> = vec![Scope::new(true)];
|
|
55
|
-
let mut steps = vec![Step::Visit(root)];
|
|
56
|
-
while let Some(step) = steps.pop() {
|
|
57
|
-
match step {
|
|
58
|
-
Step::Enter(isolated) => scopes.push(Scope::new(isolated)),
|
|
59
|
-
Step::Leave => {
|
|
60
|
-
scopes.pop();
|
|
61
|
-
}
|
|
62
|
-
Step::Visit(node) => {
|
|
63
|
-
record(node, source, &mut scopes, &mut roles, &mut local_calls);
|
|
64
|
-
push_children(node, &mut steps);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
Self { roles, local_calls }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/// Whether a receiverless call's name is a local variable in scope, which is what makes Ruby
|
|
72
|
-
/// read `collection [0]` as an index rather than as a call handed an array.
|
|
73
|
-
pub(in crate::rules) fn names_a_local(&self, node: Node<'_>) -> bool {
|
|
74
|
-
self.local_calls.contains(&node.start_byte())
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/// Whether RuboCop's variable handlers see this node at all: an assignment target, a
|
|
78
|
-
/// parameter, or a read that resolved to a local.
|
|
79
|
-
pub(in crate::rules) fn is_variable(&self, node: Node<'_>) -> bool {
|
|
80
|
-
self.roles.contains_key(&node.start_byte())
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/// Whether the parser would build an `lvar` here rather than a receiverless `send`.
|
|
84
|
-
pub(in crate::rules) fn is_reference(&self, node: Node<'_>) -> bool {
|
|
85
|
-
self.roles.get(&node.start_byte()) == Some(&Role::Reference)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/// Whether the name is being bound: an assignment target or a parameter, which is what
|
|
89
|
-
/// `on_lvasgn` and `on_arg` between them see.
|
|
90
|
-
pub(in crate::rules) fn is_definition(&self, node: Node<'_>) -> bool {
|
|
91
|
-
self.roles.get(&node.start_byte()) == Some(&Role::Definition)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
96
|
-
enum Role {
|
|
97
|
-
/// A name being bound: what `on_lvasgn`, `on_ivasgn`, `on_arg` and the rest of the aliases see.
|
|
98
|
-
Definition,
|
|
99
|
-
/// A read of a name bound earlier, which `on_lvar` reports as well.
|
|
100
|
-
Reference,
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
struct Scope<'a> {
|
|
104
|
-
/// Whether the scope hides the names around it, as a `def` or a class body does. Blocks do
|
|
105
|
-
/// not: they close over the locals of the scope they were written in.
|
|
106
|
-
isolated: bool,
|
|
107
|
-
names: HashSet<&'a str>,
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
impl Scope<'_> {
|
|
111
|
-
fn new(isolated: bool) -> Self {
|
|
112
|
-
Self {
|
|
113
|
-
isolated,
|
|
114
|
-
names: HashSet::new(),
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
enum Step<'tree> {
|
|
120
|
-
Visit(Node<'tree>),
|
|
121
|
-
Enter(bool),
|
|
122
|
-
Leave,
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
fn record<'a>(
|
|
126
|
-
node: Node<'_>,
|
|
127
|
-
source: &'a SourceFile,
|
|
128
|
-
scopes: &mut Vec<Scope<'a>>,
|
|
129
|
-
roles: &mut HashMap<usize, Role>,
|
|
130
|
-
local_calls: &mut HashSet<usize>,
|
|
131
|
-
) {
|
|
132
|
-
match node.kind_str() {
|
|
133
|
-
"identifier" => {
|
|
134
|
-
let name = source.node_text(node);
|
|
135
|
-
match position(node) {
|
|
136
|
-
Position::Binding => {
|
|
137
|
-
define(scopes, name);
|
|
138
|
-
roles.insert(node.start_byte(), Role::Definition);
|
|
139
|
-
}
|
|
140
|
-
Position::Shadow => define(scopes, name),
|
|
141
|
-
Position::MethodName => {
|
|
142
|
-
if resolves(scopes, name) {
|
|
143
|
-
local_calls.insert(node.start_byte());
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
Position::Value => {
|
|
147
|
-
if resolves(scopes, name) {
|
|
148
|
-
roles.insert(node.start_byte(), Role::Reference);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// Only assignment reaches these; a read of `@foo` has no handler in `Naming/VariableName`.
|
|
154
|
-
"instance_variable" | "class_variable" | "global_variable" => {
|
|
155
|
-
if matches!(position(node), Position::Binding) {
|
|
156
|
-
roles.insert(node.start_byte(), Role::Definition);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// `in {key:}` binds `key` without giving it an identifier node of its own.
|
|
160
|
-
"keyword_pattern" if node.field("value").is_none() => {
|
|
161
|
-
if let Some(key) = node.field("key") {
|
|
162
|
-
define(scopes, source.node_text(key).trim_end_matches(':'));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
_ => {}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
fn define<'a>(scopes: &mut [Scope<'a>], name: &'a str) {
|
|
170
|
-
if let Some(scope) = scopes.last_mut() {
|
|
171
|
-
scope.names.insert(name);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/// Whether `name` is bound anywhere in the scope chain, stopping at the first scope that hides
|
|
176
|
-
/// its surroundings.
|
|
177
|
-
fn resolves(scopes: &[Scope<'_>], name: &str) -> bool {
|
|
178
|
-
for scope in scopes.iter().rev() {
|
|
179
|
-
if scope.names.contains(name) {
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
if scope.isolated {
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
false
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
fn push_children<'tree>(node: Node<'tree>, steps: &mut Vec<Step<'tree>>) {
|
|
190
|
-
let Some((isolated, outer_fields)) = opened_scope(node.kind_str()) else {
|
|
191
|
-
let start = steps.len();
|
|
192
|
-
let mut cursor = node.walk();
|
|
193
|
-
steps.extend(node.named_children(&mut cursor).map(Step::Visit));
|
|
194
|
-
steps[start..].reverse();
|
|
195
|
-
return;
|
|
196
|
-
};
|
|
197
|
-
let outer: Vec<Node<'tree>> = outer_fields
|
|
198
|
-
.iter()
|
|
199
|
-
.filter_map(|field| node.field(field))
|
|
200
|
-
.collect();
|
|
201
|
-
let mut cursor = node.walk();
|
|
202
|
-
let inner: Vec<Node<'tree>> = node
|
|
203
|
-
.named_children(&mut cursor)
|
|
204
|
-
.filter(|child| !outer.iter().any(|node| node.id() == child.id()))
|
|
205
|
-
.collect();
|
|
206
|
-
steps.push(Step::Leave);
|
|
207
|
-
steps.extend(inner.into_iter().rev().map(Step::Visit));
|
|
208
|
-
steps.push(Step::Enter(isolated));
|
|
209
|
-
steps.extend(outer.into_iter().rev().map(Step::Visit));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/// The scope a node opens, plus the fields that are still evaluated outside it. A class body
|
|
213
|
-
/// cannot see the locals around it, but the superclass expression written next to it can, and the
|
|
214
|
-
/// same holds for the receiver of `def obj.method`.
|
|
215
|
-
fn opened_scope(kind: &str) -> Option<(bool, &'static [&'static str])> {
|
|
216
|
-
match kind {
|
|
217
|
-
"method" => Some((true, &[])),
|
|
218
|
-
"singleton_method" => Some((true, &["object"])),
|
|
219
|
-
"class" | "module" => Some((true, &["name", "superclass"])),
|
|
220
|
-
"singleton_class" => Some((true, &["value"])),
|
|
221
|
-
"block" | "do_block" | "lambda" => Some((false, &[])),
|
|
222
|
-
_ => None,
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
enum Position {
|
|
227
|
-
/// A name the assignment or parameter list binds, and that RuboCop reports on.
|
|
228
|
-
Binding,
|
|
229
|
-
/// A name bound without RuboCop looking at it. Block-locals arrive as `shadowarg` and pattern
|
|
230
|
-
/// matches as `match_var`, and `Naming/VariableName` has a handler for neither -- but both
|
|
231
|
-
/// still make later reads of the name resolve to a local.
|
|
232
|
-
Shadow,
|
|
233
|
-
MethodName,
|
|
234
|
-
Value,
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
fn position(node: Node<'_>) -> Position {
|
|
238
|
-
let Some(parent) = node.parent() else {
|
|
239
|
-
return Position::Value;
|
|
240
|
-
};
|
|
241
|
-
match parent.kind_str() {
|
|
242
|
-
"assignment" | "operator_assignment" => bound_when(node, "left"),
|
|
243
|
-
"left_assignment_list" if spurious_assignment_list(parent) => {
|
|
244
|
-
// Only the name the real parser would have assigned to is bound; the items the
|
|
245
|
-
// grammar swallowed ahead of it are ordinary expressions.
|
|
246
|
-
if last_named_child(parent).is_some_and(|last| last.id() == node.id()) {
|
|
247
|
-
Position::Binding
|
|
248
|
-
} else {
|
|
249
|
-
Position::Value
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
"left_assignment_list" | "destructured_left_assignment" | "rest_assignment" => {
|
|
253
|
-
Position::Binding
|
|
254
|
-
}
|
|
255
|
-
"method_parameters"
|
|
256
|
-
| "block_parameters"
|
|
257
|
-
| "lambda_parameters"
|
|
258
|
-
| "destructured_parameter" => {
|
|
259
|
-
if field(node) == Some("locals") {
|
|
260
|
-
Position::Shadow
|
|
261
|
-
} else {
|
|
262
|
-
Position::Binding
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
"optional_parameter"
|
|
266
|
-
| "keyword_parameter"
|
|
267
|
-
| "splat_parameter"
|
|
268
|
-
| "hash_splat_parameter"
|
|
269
|
-
| "block_parameter" => bound_when(node, "name"),
|
|
270
|
-
"for" => bound_when(node, "pattern"),
|
|
271
|
-
"exception_variable" => Position::Binding,
|
|
272
|
-
"call" => {
|
|
273
|
-
if field(node) == Some("method") {
|
|
274
|
-
Position::MethodName
|
|
275
|
-
} else {
|
|
276
|
-
Position::Value
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
"method" | "singleton_method" => {
|
|
280
|
-
if field(node) == Some("name") {
|
|
281
|
-
Position::MethodName
|
|
282
|
-
} else {
|
|
283
|
-
Position::Value
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
"setter" | "alias" | "undef" => Position::MethodName,
|
|
287
|
-
// A pin (`in ^name`) reads an existing local instead of binding a new one.
|
|
288
|
-
"variable_reference_pattern" => Position::Value,
|
|
289
|
-
"as_pattern" => shadowed_when(node, "name"),
|
|
290
|
-
"in_clause" => shadowed_when(node, "pattern"),
|
|
291
|
-
"array_pattern"
|
|
292
|
-
| "find_pattern"
|
|
293
|
-
| "hash_pattern"
|
|
294
|
-
| "keyword_pattern"
|
|
295
|
-
| "alternative_pattern" => Position::Shadow,
|
|
296
|
-
_ => Position::Value,
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
34
|
pub(super) fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
|
|
301
35
|
let mut cursor = node.walk();
|
|
302
36
|
node.named_children(&mut cursor).last()
|
|
303
37
|
}
|
|
304
38
|
|
|
305
|
-
fn bound_when(node: Node<'_>, field_name: &str) -> Position {
|
|
306
|
-
if field(node) == Some(field_name) {
|
|
307
|
-
Position::Binding
|
|
308
|
-
} else {
|
|
309
|
-
Position::Value
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
fn shadowed_when(node: Node<'_>, field_name: &str) -> Position {
|
|
314
|
-
if field(node) == Some(field_name) {
|
|
315
|
-
Position::Shadow
|
|
316
|
-
} else {
|
|
317
|
-
Position::Value
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
39
|
/// The field name `node` occupies in its parent. `child_by_field_name` cannot answer this: a
|
|
322
40
|
/// parent may hold several children under the same field, and only the first is reachable that
|
|
323
41
|
/// way.
|
|
@@ -374,9 +92,9 @@ pub(super) fn heredocs(context: &RuleContext<'_>) -> Vec<Heredoc> {
|
|
|
374
92
|
.nodes_of("heredoc_beginning")
|
|
375
93
|
.zip(bodies)
|
|
376
94
|
.filter_map(|(opening, body)| {
|
|
377
|
-
let
|
|
378
|
-
let terminator = body
|
|
379
|
-
.
|
|
95
|
+
let _cursor = body.walk();
|
|
96
|
+
let terminator = named_children_of(body, context)
|
|
97
|
+
.into_iter()
|
|
380
98
|
.find(|child| child.kind_str() == "heredoc_end")?;
|
|
381
99
|
let (body_line, _) = context.source.line_column(body.start_byte());
|
|
382
100
|
let (end_line, _) = context.source.line_column(terminator.start_byte());
|
|
@@ -574,24 +292,41 @@ fn sexp(node: Node<'_>, source: &SourceFile, indent: usize) -> String {
|
|
|
574
292
|
}
|
|
575
293
|
}
|
|
576
294
|
|
|
295
|
+
/// The `defs` a definition is written inside, which upstream reaches through `node.parent` alone.
|
|
296
|
+
/// The grammar hangs a body on a `body_statement`, so the walk passes one to find it.
|
|
297
|
+
fn enclosing_singleton_method<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
|
|
298
|
+
let parent = node.parent()?;
|
|
299
|
+
let parent = match parent.kind_str() {
|
|
300
|
+
"body_statement" => parent.parent()?,
|
|
301
|
+
_ => parent,
|
|
302
|
+
};
|
|
303
|
+
(parent.kind_str() == "singleton_method").then_some(parent)
|
|
304
|
+
}
|
|
305
|
+
|
|
577
306
|
/// `class_emitter_method?`: a singleton method may be named after a class defined beside it, as
|
|
578
307
|
/// `def self.Foo` is next to `class Foo`. RuboCop lets that through -- the method emits the class.
|
|
579
|
-
pub(super) fn class_emitter_method
|
|
308
|
+
pub(super) fn class_emitter_method<'tree>(
|
|
309
|
+
node: Node<'tree>,
|
|
310
|
+
name: &str,
|
|
311
|
+
context: &'tree RuleContext<'_>,
|
|
312
|
+
) -> bool {
|
|
580
313
|
if node.kind_str() != "singleton_method" {
|
|
581
314
|
return false;
|
|
582
315
|
}
|
|
316
|
+
let source = context.source;
|
|
317
|
+
// `node = node.parent while node.parent.defs_type?`, which reaches the enclosing `def
|
|
318
|
+
// self.included`. Upstream's `defs` holds its body directly; the grammar puts a
|
|
319
|
+
// `body_statement` between the two, so walking the raw parent stops one level short and the
|
|
320
|
+
// class beside the *outer* definition is never seen.
|
|
583
321
|
let mut current = node;
|
|
584
|
-
while let Some(parent) = current
|
|
585
|
-
.parent()
|
|
586
|
-
.filter(|p| p.kind_str() == "singleton_method")
|
|
587
|
-
{
|
|
322
|
+
while let Some(parent) = enclosing_singleton_method(current) {
|
|
588
323
|
current = parent;
|
|
589
324
|
}
|
|
590
325
|
let Some(parent) = current.parent() else {
|
|
591
326
|
return false;
|
|
592
327
|
};
|
|
593
|
-
let
|
|
594
|
-
parent
|
|
328
|
+
let _cursor = parent.walk();
|
|
329
|
+
named_children_iter(parent, context).any(|child| {
|
|
595
330
|
child.kind_str() == "class"
|
|
596
331
|
&& child
|
|
597
332
|
.field("name")
|
|
@@ -703,7 +438,10 @@ pub(super) fn ruby_regex_to_s(value: &serde_yaml_ng::Value) -> Option<String> {
|
|
|
703
438
|
let literal = tagged.value.as_str()?;
|
|
704
439
|
split_regexp_literal(literal).unwrap_or((literal, ""))
|
|
705
440
|
}
|
|
706
|
-
|
|
441
|
+
// **A plain string is not a `Regexp`.** The message interpolates whatever the configuration
|
|
442
|
+
// holds, so a `Regex:` written without the `!ruby/regexp` tag prints exactly as written --
|
|
443
|
+
// only a real `Regexp` goes through `Regexp#to_s`.
|
|
444
|
+
other => return Some(other.as_str()?.to_owned()),
|
|
707
445
|
};
|
|
708
446
|
let enabled: String = "mix".chars().filter(|flag| flags.contains(*flag)).collect();
|
|
709
447
|
let disabled: String = "mix"
|
|
@@ -789,6 +527,27 @@ fn translate_ruby_pattern(pattern: &str) -> String {
|
|
|
789
527
|
out
|
|
790
528
|
}
|
|
791
529
|
|
|
530
|
+
/// `forbidden_pattern_regexps`: the `ForbiddenPatterns` entries compiled once.
|
|
531
|
+
///
|
|
532
|
+
/// The entries are plain pattern strings rather than `!ruby/regexp` literals, and they are matched
|
|
533
|
+
/// **anywhere in the name** -- `st_[a-z]` forbids `first_arg`.
|
|
534
|
+
pub(crate) fn forbidden_patterns(context: &RuleContext<'_>) -> Vec<&'static Regex> {
|
|
535
|
+
forbidden_patterns_named(context, "ForbiddenPatterns")
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/// The same for any list of patterns a cop reads under its own key.
|
|
539
|
+
pub(crate) fn forbidden_patterns_named(
|
|
540
|
+
context: &RuleContext<'_>,
|
|
541
|
+
key: &str,
|
|
542
|
+
) -> Vec<&'static Regex> {
|
|
543
|
+
context
|
|
544
|
+
.setting::<Vec<serde_yaml_ng::Value>>(key)
|
|
545
|
+
.unwrap_or_default()
|
|
546
|
+
.iter()
|
|
547
|
+
.filter_map(ruby_regex)
|
|
548
|
+
.collect()
|
|
549
|
+
}
|
|
550
|
+
|
|
792
551
|
#[cfg(test)]
|
|
793
552
|
mod tests {
|
|
794
553
|
use super::valid_name;
|
|
@@ -9,7 +9,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
9
9
|
.unwrap_or_else(|| "snake_case".to_owned());
|
|
10
10
|
let allowed: Vec<String> = context.setting("AllowedIdentifiers").unwrap_or_default();
|
|
11
11
|
let forbidden: Vec<String> = context.setting("ForbiddenIdentifiers").unwrap_or_default();
|
|
12
|
-
|
|
12
|
+
// `forbidden_name?` is `forbidden_identifier? || forbidden_pattern?`: a name may be forbidden
|
|
13
|
+
// by an exact spelling or by a pattern it matches anywhere.
|
|
14
|
+
let patterns = super::support::forbidden_patterns(context);
|
|
15
|
+
// `valid_name?` is `super || matches_allowed_pattern?(name)`: the allowed patterns excuse a
|
|
16
|
+
// name from the **enforced style** only. A forbidden name stays forbidden however it is spelt.
|
|
17
|
+
let allowed_patterns = super::support::forbidden_patterns_named(context, "AllowedPatterns");
|
|
18
|
+
let variables = context.variable_analysis();
|
|
13
19
|
for node in context.nodes_of_any(&[
|
|
14
20
|
"identifier",
|
|
15
21
|
"instance_variable",
|
|
@@ -20,7 +26,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
20
26
|
continue;
|
|
21
27
|
}
|
|
22
28
|
let name = context.source.node_text(node);
|
|
23
|
-
|
|
29
|
+
// `SIGILS = '@$'`: both `allowed_identifier?` and `forbidden_identifier?` strip them before
|
|
30
|
+
// matching, so `AllowedIdentifiers: [first_arg]` covers `@first_arg` too. **The patterns do
|
|
31
|
+
// not** -- `forbidden_pattern?` is given the name as written.
|
|
32
|
+
let bare = name.replace(['@', '$'], "");
|
|
33
|
+
let forbidden_name = forbidden.contains(&bare)
|
|
34
|
+
|| patterns.iter().any(|pattern| pattern.is_match(name));
|
|
24
35
|
// `on_gvasgn` stops at the forbidden names: a global variable's spelling is never held
|
|
25
36
|
// against the enforced style.
|
|
26
37
|
if node.kind_str() == "global_variable" {
|
|
@@ -29,12 +40,14 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
29
40
|
}
|
|
30
41
|
continue;
|
|
31
42
|
}
|
|
32
|
-
if allowed.
|
|
43
|
+
if allowed.contains(&bare) {
|
|
33
44
|
continue;
|
|
34
45
|
}
|
|
35
46
|
if forbidden_name {
|
|
36
47
|
offenses.push(forbidden_offense(context, node, name));
|
|
37
|
-
} else if !valid_name(name, &style)
|
|
48
|
+
} else if !valid_name(name, &style)
|
|
49
|
+
&& !allowed_patterns.iter().any(|pattern| pattern.is_match(name))
|
|
50
|
+
{
|
|
38
51
|
offenses.push(context.offense(
|
|
39
52
|
format!("Use {style} for variable names."),
|
|
40
53
|
node.byte_range(),
|
|
@@ -5,12 +5,13 @@ use regex::Regex;
|
|
|
5
5
|
use tree_sitter::Node;
|
|
6
6
|
|
|
7
7
|
use super::support::{
|
|
8
|
-
PARAMETER_LISTS, ParameterKind, bound_parameters, class_emitter_method,
|
|
9
|
-
|
|
8
|
+
PARAMETER_LISTS, ParameterKind, bound_parameters, class_emitter_method, quoted_content,
|
|
9
|
+
ruby_regex,
|
|
10
10
|
};
|
|
11
11
|
use crate::diagnostic::Offense;
|
|
12
12
|
use crate::rules::RuleContext;
|
|
13
13
|
use crate::rules::node_ext::NodeExt;
|
|
14
|
+
use crate::rules::send_node::named_children_iter;
|
|
14
15
|
|
|
15
16
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
16
17
|
let style: String = context
|
|
@@ -44,7 +45,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
let variables = context.
|
|
48
|
+
let variables = context.variable_analysis();
|
|
48
49
|
for node in context.nodes_of_any(&[
|
|
49
50
|
"identifier",
|
|
50
51
|
"instance_variable",
|
|
@@ -89,7 +90,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
89
90
|
}
|
|
90
91
|
if valid_number(&name, &style)
|
|
91
92
|
|| patterns.iter().any(|pattern| pattern.is_match(&name))
|
|
92
|
-
|| definition.is_some_and(|node| class_emitter_method(node, &name, context
|
|
93
|
+
|| definition.is_some_and(|node| class_emitter_method(node, &name, context))
|
|
93
94
|
{
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
@@ -160,8 +161,7 @@ fn collect_symbols<'tree>(
|
|
|
160
161
|
// builds no symbol at all.
|
|
161
162
|
"hash_key_symbol" => {
|
|
162
163
|
let bare_pattern = node.parent_of(context).is_some_and(|parent| {
|
|
163
|
-
parent.kind_str() == "keyword_pattern"
|
|
164
|
-
&& parent.field("value").is_none()
|
|
164
|
+
parent.kind_str() == "keyword_pattern" && parent.field("value").is_none()
|
|
165
165
|
});
|
|
166
166
|
if !bare_pattern {
|
|
167
167
|
push(node.byte_range(), context.source.node_text(node).to_owned());
|
|
@@ -179,8 +179,8 @@ fn collect_symbols<'tree>(
|
|
|
179
179
|
// `alias foo bar` and `undef foo` name methods with symbols the parser invents; only
|
|
180
180
|
// an alias between global variables does not.
|
|
181
181
|
_ => {
|
|
182
|
-
let
|
|
183
|
-
for child in node
|
|
182
|
+
let _cursor = node.walk();
|
|
183
|
+
for child in named_children_iter(node, context) {
|
|
184
184
|
if matches!(
|
|
185
185
|
child.kind_str(),
|
|
186
186
|
"identifier" | "constant" | "operator" | "setter"
|
|
@@ -206,7 +206,6 @@ fn is_quoted_label(node: Node<'_>) -> bool {
|
|
|
206
206
|
return false;
|
|
207
207
|
}
|
|
208
208
|
let mut cursor = parent.walk();
|
|
209
|
-
parent
|
|
210
|
-
.children(&mut cursor)
|
|
209
|
+
parent.children(&mut cursor)
|
|
211
210
|
.any(|child| child.kind_str() == ":")
|
|
212
211
|
}
|
data/src/rules/node_ext.rs
CHANGED
data/src/rules/ruby_literal.rs
CHANGED
|
@@ -9,6 +9,7 @@ use tree_sitter::Node;
|
|
|
9
9
|
|
|
10
10
|
use crate::rules::RuleContext;
|
|
11
11
|
use crate::rules::node_ext::NodeExt;
|
|
12
|
+
use crate::rules::send_node::named_children_iter;
|
|
12
13
|
|
|
13
14
|
/// The names a method may be given that are not identifiers, which Ruby writes a symbol of bare.
|
|
14
15
|
const OPERATOR_NAMES: &[&str] = &[
|
|
@@ -69,7 +70,8 @@ fn is_bare_identifier(name: &str, method: bool) -> bool {
|
|
|
69
70
|
pub(crate) fn inspect_string(value: &str) -> String {
|
|
70
71
|
let mut out = String::with_capacity(value.len() + 2);
|
|
71
72
|
out.push('"');
|
|
72
|
-
|
|
73
|
+
let mut characters = value.chars().peekable();
|
|
74
|
+
while let Some(character) = characters.next() {
|
|
73
75
|
match character {
|
|
74
76
|
'"' => out.push_str("\\\""),
|
|
75
77
|
'\\' => out.push_str("\\\\"),
|
|
@@ -82,6 +84,9 @@ pub(crate) fn inspect_string(value: &str) -> String {
|
|
|
82
84
|
'\u{c}' => out.push_str("\\f"),
|
|
83
85
|
'\u{1b}' => out.push_str("\\e"),
|
|
84
86
|
'\0' => out.push_str("\\0"),
|
|
87
|
+
// `String#inspect` escapes the `#` that would open an interpolation, so what it
|
|
88
|
+
// returns stands for the same string when pasted back into double quotes.
|
|
89
|
+
'#' if matches!(characters.peek(), Some('{' | '@' | '$')) => out.push_str("\\#"),
|
|
85
90
|
other => out.push(other),
|
|
86
91
|
}
|
|
87
92
|
}
|
|
@@ -95,8 +100,8 @@ pub(crate) fn string_value(node: Node<'_>, context: &RuleContext<'_>) -> String
|
|
|
95
100
|
let raw = context.source.node_text(node);
|
|
96
101
|
let verbatim = raw.starts_with('\'') || raw.starts_with("%q") || raw.starts_with(":'");
|
|
97
102
|
let mut value = String::new();
|
|
98
|
-
let
|
|
99
|
-
for child in node
|
|
103
|
+
let _cursor = node.walk();
|
|
104
|
+
for child in named_children_iter(node, context) {
|
|
100
105
|
match child.kind_str() {
|
|
101
106
|
"string_content" if verbatim => {
|
|
102
107
|
push_verbatim(context.source.node_text(child), &mut value);
|
|
@@ -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::{arguments, is_plain_send, named_children, send_range, symbol_name};
|
|
9
|
+
use crate::rules::send_node::all_children_iter;
|
|
9
10
|
|
|
10
11
|
const COMBINATOR_IN_HASH_MSG: &str = "Use `[...].hash` instead of combining hash values manually.";
|
|
11
12
|
const MONUPLE_HASH_MSG: &str =
|
|
@@ -186,8 +187,8 @@ fn element_count(node: Node<'_>) -> usize {
|
|
|
186
187
|
|
|
187
188
|
/// The operator a node was written with, which the grammar leaves as an anonymous token.
|
|
188
189
|
fn operator<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> Option<&'a str> {
|
|
189
|
-
let
|
|
190
|
-
node
|
|
190
|
+
let _cursor = node.walk();
|
|
191
|
+
all_children_iter(node, context)
|
|
191
192
|
.find(|child| !child.is_named())
|
|
192
193
|
.map(|child| context.source.node_text(child))
|
|
193
194
|
}
|
data/src/rules/security/open.rs
CHANGED
|
@@ -3,10 +3,11 @@ use tree_sitter::Node;
|
|
|
3
3
|
use crate::diagnostic::Offense;
|
|
4
4
|
use crate::rules::RuleContext;
|
|
5
5
|
use crate::rules::send_node::{
|
|
6
|
-
arguments, heredoc_body, is_plain_send, is_string,
|
|
6
|
+
arguments, heredoc_body, is_plain_send, is_string, string_text,
|
|
7
7
|
top_level_constant,
|
|
8
8
|
};
|
|
9
9
|
use crate::rules::node_ext::NodeExt;
|
|
10
|
+
use crate::rules::send_node::named_children_of;
|
|
10
11
|
|
|
11
12
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
12
13
|
for node in context.nodes_of("call") {
|
|
@@ -53,7 +54,7 @@ fn safe(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
|
53
54
|
// with the interpolation itself has no literal part and stays unsafe.
|
|
54
55
|
"string" => safe_argument(&leading_literal(node, context)),
|
|
55
56
|
// `"a" "b"` reaches upstream as a `dstr` of the two literals, so the first one decides.
|
|
56
|
-
"chained_string" =>
|
|
57
|
+
"chained_string" => named_children_of(node, context)
|
|
57
58
|
.first()
|
|
58
59
|
.is_some_and(|first| safe(*first, context)),
|
|
59
60
|
"heredoc_beginning" => {
|
|
@@ -81,7 +82,7 @@ fn safe_argument(argument: &str) -> bool {
|
|
|
81
82
|
/// every adjacent literal part into one `str`, so escapes and plain runs count together.
|
|
82
83
|
fn leading_literal(node: Node<'_>, context: &RuleContext<'_>) -> String {
|
|
83
84
|
let mut text = String::new();
|
|
84
|
-
for child in
|
|
85
|
+
for child in named_children_of(node, context) {
|
|
85
86
|
if child.kind_str() == "interpolation" {
|
|
86
87
|
break;
|
|
87
88
|
}
|
|
@@ -95,7 +96,7 @@ fn leading_literal(node: Node<'_>, context: &RuleContext<'_>) -> String {
|
|
|
95
96
|
/// when it was opened with `<<~`.
|
|
96
97
|
fn heredoc_leading_text(beginning: Node<'_>, context: &RuleContext<'_>) -> Option<String> {
|
|
97
98
|
let body = heredoc_body(beginning, context)?;
|
|
98
|
-
let children =
|
|
99
|
+
let children = named_children_of(body, context);
|
|
99
100
|
let boundary = |kinds: &[&str]| {
|
|
100
101
|
children
|
|
101
102
|
.iter()
|