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
|
@@ -18,7 +18,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
18
18
|
// unreported even when they hold non-ascii characters. tree-sitter has no such token
|
|
19
19
|
// distinction: it reuses `identifier` for keyword-argument labels, which are `tLABEL`
|
|
20
20
|
// upstream and must be skipped here to keep the two in step.
|
|
21
|
-
if is_keyword_argument_label(node) {
|
|
21
|
+
if is_keyword_argument_label(node, context) {
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -42,8 +42,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
fn is_keyword_argument_label(node: tree_sitter::Node<'_>) -> bool {
|
|
46
|
-
let Some(parent) =
|
|
45
|
+
fn is_keyword_argument_label(node: tree_sitter::Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
46
|
+
let Some(parent) = context.parent(node) else {
|
|
47
47
|
return false;
|
|
48
48
|
};
|
|
49
49
|
matches!(parent.kind_str(), "keyword_parameter" | "pair")
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
use tree_sitter::Node;
|
|
2
2
|
|
|
3
|
-
use super::support::{ParameterKind,
|
|
3
|
+
use super::support::{ParameterKind, parameters};
|
|
4
4
|
use crate::diagnostic::{Edit, Offense};
|
|
5
5
|
use crate::rules::RuleContext;
|
|
6
|
+
use crate::rules::lint::variable_force::Analysis;
|
|
6
7
|
use crate::rules::node_ext::NodeExt;
|
|
7
8
|
|
|
8
9
|
/// `EXCLUDED`: operators whose sole parameter is not the other operand. Indexing takes a key,
|
|
@@ -38,7 +39,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
38
39
|
if text == "other" || text == "_other" {
|
|
39
40
|
continue;
|
|
40
41
|
}
|
|
41
|
-
let variables = variables.get_or_insert_with(|| context.
|
|
42
|
+
let variables = variables.get_or_insert_with(|| context.variable_analysis());
|
|
42
43
|
offenses.push(
|
|
43
44
|
context
|
|
44
45
|
.offense(
|
|
@@ -59,7 +60,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
59
60
|
/// one and is put off to the next pass.
|
|
60
61
|
fn rename(
|
|
61
62
|
context: &RuleContext<'_>,
|
|
62
|
-
variables: &
|
|
63
|
+
variables: &Analysis<'_>,
|
|
63
64
|
definition: Node<'_>,
|
|
64
65
|
parameter: Node<'_>,
|
|
65
66
|
) -> Vec<Edit> {
|
|
@@ -69,7 +70,7 @@ fn rename(
|
|
|
69
70
|
if node.start_byte() <= parameter.start_byte()
|
|
70
71
|
|| node.end_byte() > definition.end_byte()
|
|
71
72
|
|| context.source.node_text(node) != name
|
|
72
|
-
|| !variables.
|
|
73
|
+
|| !(variables.is_reference(node) || variables.is_local_assignment(node))
|
|
73
74
|
{
|
|
74
75
|
continue;
|
|
75
76
|
}
|
|
@@ -4,8 +4,9 @@ use crate::diagnostic::{Edit, Offense};
|
|
|
4
4
|
use crate::ruby_version::RubyVersion;
|
|
5
5
|
use crate::rules::RuleContext;
|
|
6
6
|
use crate::rules::node_ext::NodeExt;
|
|
7
|
-
use crate::rules::push_named_children;
|
|
8
|
-
use crate::rules::send_node::
|
|
7
|
+
use crate::rules::{push_named_children, push_named_children_in};
|
|
8
|
+
use crate::rules::send_node::send_range;
|
|
9
|
+
use crate::rules::send_node::named_children_of;
|
|
9
10
|
|
|
10
11
|
/// `minimum_target_ruby_version 3.1`: anonymous block forwarding is 3.1 syntax.
|
|
11
12
|
const MINIMUM: RubyVersion = RubyVersion::new(3, 1);
|
|
@@ -42,7 +43,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
42
43
|
let Some(parameters) = node.field("parameters") else {
|
|
43
44
|
continue;
|
|
44
45
|
};
|
|
45
|
-
let Some(last) =
|
|
46
|
+
let Some(last) = named_children_of(parameters, context).last().copied() else {
|
|
46
47
|
continue;
|
|
47
48
|
};
|
|
48
49
|
if expected_style(node, last, anonymous, context) {
|
|
@@ -96,13 +97,13 @@ fn expected_style(
|
|
|
96
97
|
fn forwarded_arguments<'tree>(
|
|
97
98
|
node: Node<'tree>,
|
|
98
99
|
last: Node<'tree>,
|
|
99
|
-
context: &RuleContext<'_>,
|
|
100
|
+
context: &'tree RuleContext<'_>,
|
|
100
101
|
) -> Option<Vec<Node<'tree>>> {
|
|
101
102
|
let unsafe_in_block = context.target_ruby_version() <= ANONYMOUS_IN_BLOCK_REJECTED_THROUGH;
|
|
102
103
|
let name = context.source.node_text(last);
|
|
103
104
|
let mut found = Vec::new();
|
|
104
105
|
let mut stack = Vec::new();
|
|
105
|
-
|
|
106
|
+
push_named_children_in(node, context, &mut stack);
|
|
106
107
|
while let Some(current) = stack.pop() {
|
|
107
108
|
if current.kind_str() == "block_argument" {
|
|
108
109
|
// `invalidates_syntax?` is asked of every block pass, matching or not, and aborts the
|
|
@@ -111,14 +112,14 @@ fn forwarded_arguments<'tree>(
|
|
|
111
112
|
return None;
|
|
112
113
|
}
|
|
113
114
|
// `&:sym` forwards a symbol rather than the block parameter.
|
|
114
|
-
let symbolic =
|
|
115
|
+
let symbolic = named_children_of(current, context).first().is_some_and(|child| {
|
|
115
116
|
matches!(child.kind_str(), "simple_symbol" | "delimited_symbol")
|
|
116
117
|
});
|
|
117
118
|
if !symbolic && context.source.node_text(current) == name {
|
|
118
119
|
found.push(current);
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
|
-
|
|
122
|
+
push_named_children_in(current, context, &mut stack);
|
|
122
123
|
}
|
|
123
124
|
Some(found)
|
|
124
125
|
}
|
|
@@ -313,7 +314,7 @@ fn reads_as_variable(node: Node<'_>, name: Option<&str>, context: &RuleContext<'
|
|
|
313
314
|
{
|
|
314
315
|
return true;
|
|
315
316
|
}
|
|
316
|
-
|
|
317
|
+
push_named_children_in(current, context, &mut stack);
|
|
317
318
|
}
|
|
318
319
|
false
|
|
319
320
|
}
|
|
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
|
|
|
3
3
|
use regex::Regex;
|
|
4
4
|
use tree_sitter::Node;
|
|
5
5
|
|
|
6
|
-
use super::support::
|
|
6
|
+
use super::support::last_named_child;
|
|
7
7
|
use crate::diagnostic::Offense;
|
|
8
8
|
use crate::rules::RuleContext;
|
|
9
9
|
use crate::rules::node_ext::NodeExt;
|
|
@@ -15,17 +15,16 @@ static SCREAMING_SNAKE_CASE: LazyLock<Regex> =
|
|
|
15
15
|
LazyLock::new(|| Regex::new(r"^[0-9\p{Uppercase}_]+$").unwrap());
|
|
16
16
|
|
|
17
17
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
18
|
-
let variables = context.variable_roles();
|
|
19
18
|
// `rescue => Foo` and `for Foo in bar` are `casgn` nodes upstream just as much as `Foo = bar`
|
|
20
19
|
// is, and neither carries a value, so neither can be excused by what it was assigned.
|
|
21
20
|
for node in context.nodes_of("exception_variable") {
|
|
22
21
|
if let Some(target) = node.named_child(0) {
|
|
23
|
-
report(context, offenses, target, None
|
|
22
|
+
report(context, offenses, target, None);
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
for node in context.nodes_of("for") {
|
|
27
26
|
if let Some(target) = node.field("pattern") {
|
|
28
|
-
report(context, offenses, target, None
|
|
27
|
+
report(context, offenses, target, None);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
for node in context.nodes_of_any(&["assignment", "operator_assignment"]) {
|
|
@@ -45,7 +44,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
45
44
|
// The grammar swallowed the items written before the assignment; only the last of them
|
|
46
45
|
// is really assigned to, and it keeps the value on the right.
|
|
47
46
|
if let Some(target) = last_named_child(left) {
|
|
48
|
-
report(context, offenses, target, value
|
|
47
|
+
report(context, offenses, target, value);
|
|
49
48
|
}
|
|
50
49
|
} else if left.kind_str() == "left_assignment_list" {
|
|
51
50
|
// Every target of a multiple assignment is a casgn without an expression, so none of
|
|
@@ -53,10 +52,10 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
53
52
|
let mut targets = Vec::new();
|
|
54
53
|
collect_targets(left, &mut targets);
|
|
55
54
|
for target in targets {
|
|
56
|
-
report(context, offenses, target, None
|
|
55
|
+
report(context, offenses, target, None);
|
|
57
56
|
}
|
|
58
57
|
} else {
|
|
59
|
-
report(context, offenses, left, value
|
|
58
|
+
report(context, offenses, left, value);
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
}
|
|
@@ -66,12 +65,11 @@ fn report(
|
|
|
66
65
|
offenses: &mut Vec<Offense>,
|
|
67
66
|
target: Node<'_>,
|
|
68
67
|
value: Option<Node<'_>>,
|
|
69
|
-
variables: &Variables,
|
|
70
68
|
) {
|
|
71
69
|
let Some(name_node) = constant_name(target) else {
|
|
72
70
|
return;
|
|
73
71
|
};
|
|
74
|
-
if allowed_assignment(value,
|
|
72
|
+
if allowed_assignment(value, context) {
|
|
75
73
|
return;
|
|
76
74
|
}
|
|
77
75
|
if SCREAMING_SNAKE_CASE.is_match(context.source.node_text(name_node)) {
|
|
@@ -110,11 +108,11 @@ fn collect_targets<'tree>(node: Node<'tree>, targets: &mut Vec<Node<'tree>>) {
|
|
|
110
108
|
/// The shapes `allowed_assignment?` lets through. RuboCop only judges a constant's spelling once
|
|
111
109
|
/// it can tell the value is not a class or module, because `SomeClass = SomeOtherClass` is a
|
|
112
110
|
/// perfectly good CamelCase constant.
|
|
113
|
-
fn allowed_assignment(value: Option<Node<'_>>,
|
|
111
|
+
fn allowed_assignment(value: Option<Node<'_>>, context: &RuleContext<'_>) -> bool {
|
|
114
112
|
let Some(value) = value else {
|
|
115
113
|
return false;
|
|
116
114
|
};
|
|
117
|
-
match value_kind(value,
|
|
115
|
+
match value_kind(value, context) {
|
|
118
116
|
Value::ClassLike => true,
|
|
119
117
|
Value::Call { receiver } => receiver.is_none_or(|receiver| !literal_receiver(receiver)),
|
|
120
118
|
Value::Conditional => branches(value).into_iter().any(is_constant),
|
|
@@ -133,7 +131,7 @@ enum Value<'tree> {
|
|
|
133
131
|
Other,
|
|
134
132
|
}
|
|
135
133
|
|
|
136
|
-
fn value_kind<'tree>(node: Node<'tree>,
|
|
134
|
+
fn value_kind<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Value<'tree> {
|
|
137
135
|
match node.kind_str() {
|
|
138
136
|
"constant" => Value::ClassLike,
|
|
139
137
|
"scope_resolution" => match node.field("name") {
|
|
@@ -148,14 +146,28 @@ fn value_kind<'tree>(node: Node<'tree>, variables: &Variables) -> Value<'tree> {
|
|
|
148
146
|
_ => Value::Other,
|
|
149
147
|
},
|
|
150
148
|
// A method call carrying a block is a `block` node upstream, not a `send`.
|
|
151
|
-
|
|
149
|
+
//
|
|
150
|
+
// **`numblock` and `itblock` are not `block`.** A block using `_1` or `it` reaches upstream
|
|
151
|
+
// as a type of its own, which the `%i[block const casgn]` list does not name -- so
|
|
152
|
+
// `Foo = Module.new do a(_1) end` is reported where the same block spelling its parameter
|
|
153
|
+
// is not.
|
|
154
|
+
// **A `numblock` passes every allowance.** It is not in `%i[block const casgn]`, it is no
|
|
155
|
+
// `send`, and `class_or_struct_return_method?` never reaches it -- so upstream reports the
|
|
156
|
+
// constant that a plain block would excuse.
|
|
157
|
+
"call" if node.field("block").is_some() => match node
|
|
158
|
+
.field("block")
|
|
159
|
+
.is_some_and(|block| is_numbered_block(context, block))
|
|
160
|
+
{
|
|
161
|
+
true => Value::Other,
|
|
162
|
+
false => Value::ClassLike,
|
|
163
|
+
},
|
|
152
164
|
"lambda" => Value::ClassLike,
|
|
153
165
|
"call" => Value::Call {
|
|
154
166
|
receiver: node.field("receiver"),
|
|
155
167
|
},
|
|
156
168
|
// A bare identifier is a receiverless call unless the parser resolved it to a local.
|
|
157
169
|
"identifier" => {
|
|
158
|
-
if
|
|
170
|
+
if context.variable_analysis().is_reference(node) {
|
|
159
171
|
Value::Other
|
|
160
172
|
} else {
|
|
161
173
|
Value::Call { receiver: None }
|
|
@@ -171,9 +183,7 @@ fn value_kind<'tree>(node: Node<'tree>, variables: &Variables) -> Value<'tree> {
|
|
|
171
183
|
"unary" => match operator(node) {
|
|
172
184
|
Some("defined?") => Value::Other,
|
|
173
185
|
// A signed number reaches the parser as one numeric literal, not a call to `-@`.
|
|
174
|
-
Some("-" | "+") if node.field("operand").is_some_and(numeric) =>
|
|
175
|
-
Value::Other
|
|
176
|
-
}
|
|
186
|
+
Some("-" | "+") if node.field("operand").is_some_and(numeric) => Value::Other,
|
|
177
187
|
_ => Value::Call {
|
|
178
188
|
receiver: node.field("operand"),
|
|
179
189
|
},
|
|
@@ -227,7 +237,10 @@ fn is_literal(node: Node<'_>) -> bool {
|
|
|
227
237
|
}
|
|
228
238
|
|
|
229
239
|
fn numeric(node: Node<'_>) -> bool {
|
|
230
|
-
matches!(
|
|
240
|
+
matches!(
|
|
241
|
+
node.kind_str(),
|
|
242
|
+
"integer" | "float" | "rational" | "complex"
|
|
243
|
+
)
|
|
231
244
|
}
|
|
232
245
|
|
|
233
246
|
fn is_constant(node: Node<'_>) -> bool {
|
|
@@ -282,3 +295,28 @@ fn operator(node: Node<'_>) -> Option<&'static str> {
|
|
|
282
295
|
}
|
|
283
296
|
}
|
|
284
297
|
}
|
|
298
|
+
|
|
299
|
+
/// The version `it` becomes a block parameter rather than a method call.
|
|
300
|
+
const IT_VERSION: crate::ruby_version::RubyVersion = crate::ruby_version::RubyVersion::new(3, 4);
|
|
301
|
+
|
|
302
|
+
/// `numblock` / `itblock`: a block with no parameter list that names `_1`…`_9` or `it` instead.
|
|
303
|
+
fn is_numbered_block(context: &RuleContext<'_>, block: Node<'_>) -> bool {
|
|
304
|
+
if block.field("parameters").is_some() {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
let mut found = false;
|
|
308
|
+
crate::rules::walk_named(block, context, &mut |node| {
|
|
309
|
+
if found || node.kind_str() != "identifier" {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
let text = context.source.node_text(node);
|
|
313
|
+
// `it` is an `itblock` only from 3.4 on; before that it is an ordinary method call, and
|
|
314
|
+
// the block stays a `block`.
|
|
315
|
+
found |= (context.target_ruby_version() >= IT_VERSION && text == "it")
|
|
316
|
+
|| (text.len() == 2
|
|
317
|
+
&& text.starts_with('_')
|
|
318
|
+
&& text.as_bytes()[1].is_ascii_digit()
|
|
319
|
+
&& text.as_bytes()[1] != b'0');
|
|
320
|
+
});
|
|
321
|
+
found
|
|
322
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
use std::path::Path;
|
|
2
1
|
use std::sync::LazyLock;
|
|
3
2
|
|
|
4
|
-
use globset::Glob;
|
|
5
3
|
use regex::Regex;
|
|
4
|
+
use tree_sitter::Node;
|
|
6
5
|
|
|
7
6
|
use super::support::{ruby_regex, ruby_regex_to_s};
|
|
8
7
|
use crate::diagnostic::Offense;
|
|
9
8
|
use crate::rules::RuleContext;
|
|
9
|
+
use crate::rules::node_ext::NodeExt;
|
|
10
10
|
|
|
11
11
|
/// `SNAKE_CASE`, whose POSIX class is Unicode-aware in Ruby. A dot is allowed because only the
|
|
12
12
|
/// last extension is stripped before the name is judged.
|
|
@@ -15,7 +15,7 @@ static SNAKE_CASE: LazyLock<Regex> =
|
|
|
15
15
|
|
|
16
16
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
17
17
|
let path = context.source.path();
|
|
18
|
-
if allowed_camel_case_file(
|
|
18
|
+
if context.config.allowed_camel_case_file(path) {
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
let Some(basename) = path.file_name().and_then(|name| name.to_str()) else {
|
|
@@ -25,9 +25,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
25
25
|
.setting::<serde_yaml_ng::Value>("Regex")
|
|
26
26
|
.as_ref()
|
|
27
27
|
.and_then(ruby_regex);
|
|
28
|
-
// `
|
|
29
|
-
//
|
|
28
|
+
// `for_bad_filename` branches both ways: a name that *is* good is where the class-and-module
|
|
29
|
+
// checks run, and a name that is not is where the naming message comes from. Returning early
|
|
30
|
+
// on a good name skipped `ExpectMatchingDefinition` entirely.
|
|
30
31
|
if filename_good(basename, pattern.unwrap_or(&SNAKE_CASE)) {
|
|
32
|
+
if let Some(message) = class_and_module_naming(context, path, basename) {
|
|
33
|
+
offenses.push(context.offense(message, 0..0));
|
|
34
|
+
}
|
|
31
35
|
return;
|
|
32
36
|
}
|
|
33
37
|
let ignore_scripts: bool = context.setting("IgnoreExecutableScripts").unwrap_or(true);
|
|
@@ -47,6 +51,171 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
47
51
|
offenses.push(context.offense(message, 0..0));
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
/// `perform_class_and_module_naming_checks`: with `ExpectMatchingDefinition` on, a well-named file
|
|
55
|
+
/// still has to define the constant its name spells.
|
|
56
|
+
fn class_and_module_naming(
|
|
57
|
+
context: &RuleContext<'_>,
|
|
58
|
+
path: &std::path::Path,
|
|
59
|
+
basename: &str,
|
|
60
|
+
) -> Option<String> {
|
|
61
|
+
if !context
|
|
62
|
+
.setting::<bool>("ExpectMatchingDefinition")
|
|
63
|
+
.unwrap_or(false)
|
|
64
|
+
{
|
|
65
|
+
return None;
|
|
66
|
+
}
|
|
67
|
+
let hierarchy: bool = context
|
|
68
|
+
.setting("CheckDefinitionPathHierarchy")
|
|
69
|
+
.unwrap_or(true);
|
|
70
|
+
let acronyms: Vec<String> = context.setting("AllowedAcronyms").unwrap_or_default();
|
|
71
|
+
// The two arms differ only in what the namespace is read off: the whole path, or the base name
|
|
72
|
+
// alone. The message quotes whichever was used.
|
|
73
|
+
let (namespace, quoted) = match hierarchy {
|
|
74
|
+
true => (
|
|
75
|
+
to_namespace(context, path),
|
|
76
|
+
path.to_string_lossy().into_owned(),
|
|
77
|
+
),
|
|
78
|
+
false => (
|
|
79
|
+
to_namespace(context, std::path::Path::new(basename)),
|
|
80
|
+
basename.to_owned(),
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
if finds_definition(context, &namespace, &acronyms) {
|
|
84
|
+
return None;
|
|
85
|
+
}
|
|
86
|
+
let _ = quoted;
|
|
87
|
+
Some(format!(
|
|
88
|
+
"`{basename}` should define a class or module called `{}`.",
|
|
89
|
+
namespace.join("::")
|
|
90
|
+
))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// `to_namespace`: the path split into constant names, starting at the innermost directory the
|
|
94
|
+
/// configuration calls a root. With no root in the path only the file's own name is used.
|
|
95
|
+
fn to_namespace(context: &RuleContext<'_>, path: &std::path::Path) -> Vec<String> {
|
|
96
|
+
let roots: Vec<String> = context
|
|
97
|
+
.setting("CheckDefinitionPathHierarchyRoots")
|
|
98
|
+
.unwrap_or_else(|| ["lib", "spec", "test", "src"].map(str::to_owned).to_vec());
|
|
99
|
+
let components: Vec<String> = path
|
|
100
|
+
.iter()
|
|
101
|
+
.map(|part| part.to_string_lossy().into_owned())
|
|
102
|
+
.collect();
|
|
103
|
+
let start = components
|
|
104
|
+
.iter()
|
|
105
|
+
.rposition(|part| roots.contains(part))
|
|
106
|
+
.map(|index| index + 1);
|
|
107
|
+
match start {
|
|
108
|
+
Some(index) if index < components.len() => components[index..]
|
|
109
|
+
.iter()
|
|
110
|
+
.map(|part| module_name(part))
|
|
111
|
+
.collect(),
|
|
112
|
+
_ => components
|
|
113
|
+
.last()
|
|
114
|
+
.map(|part| vec![module_name(part)])
|
|
115
|
+
.unwrap_or_default(),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// `to_module_name`: the extension goes, the underscores split words, each word is capitalised.
|
|
120
|
+
fn module_name(basename: &str) -> String {
|
|
121
|
+
let stem = basename.split_once('.').map_or(basename, |(head, _)| head);
|
|
122
|
+
stem.split('_')
|
|
123
|
+
.map(|word| {
|
|
124
|
+
let mut characters = word.chars();
|
|
125
|
+
match characters.next() {
|
|
126
|
+
Some(first) => first.to_uppercase().collect::<String>() + characters.as_str(),
|
|
127
|
+
None => String::new(),
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
.collect()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// `find_class_or_module`: whether the file defines the innermost name, nested in the rest.
|
|
134
|
+
///
|
|
135
|
+
/// The nesting is checked by name rather than by walking upwards the way upstream does -- the
|
|
136
|
+
/// namespaces a file writes are what `class A::B` and `module A; module B` both spell out.
|
|
137
|
+
fn finds_definition(context: &RuleContext<'_>, namespace: &[String], acronyms: &[String]) -> bool {
|
|
138
|
+
let Some((name, outer)) = namespace.split_last() else {
|
|
139
|
+
return true;
|
|
140
|
+
};
|
|
141
|
+
context
|
|
142
|
+
.nodes_of_any(&["class", "module", "assignment"])
|
|
143
|
+
.any(|node| {
|
|
144
|
+
let Some(defined) = defined_constant(context, node) else {
|
|
145
|
+
return false;
|
|
146
|
+
};
|
|
147
|
+
let Some((last, prefix)) = defined.split_last() else {
|
|
148
|
+
return false;
|
|
149
|
+
};
|
|
150
|
+
if last != name && !matches_acronym(name, last, acronyms) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
outer.is_empty() || enclosing_names(context, node, prefix).ends_with(outer)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// The constant a `class` / `module` / `Struct.new` assignment defines, as its written path.
|
|
158
|
+
fn defined_constant(context: &RuleContext<'_>, node: Node<'_>) -> Option<Vec<String>> {
|
|
159
|
+
let named = match node.kind_str() {
|
|
160
|
+
"class" | "module" => node.field("name")?,
|
|
161
|
+
// `Foo = Struct.new(...)` defines `Foo` the same way.
|
|
162
|
+
"assignment" => {
|
|
163
|
+
let left = node.field("left")?;
|
|
164
|
+
let right = node.field("right")?;
|
|
165
|
+
let struct_new = right.kind_str() == "call"
|
|
166
|
+
&& right
|
|
167
|
+
.field("receiver")
|
|
168
|
+
.is_some_and(|receiver| context.source.node_text(receiver) == "Struct")
|
|
169
|
+
&& right
|
|
170
|
+
.field("method")
|
|
171
|
+
.is_some_and(|method| context.source.node_text(method) == "new");
|
|
172
|
+
if !struct_new {
|
|
173
|
+
return None;
|
|
174
|
+
}
|
|
175
|
+
left
|
|
176
|
+
}
|
|
177
|
+
_ => return None,
|
|
178
|
+
};
|
|
179
|
+
let text = context.source.node_text(named);
|
|
180
|
+
Some(
|
|
181
|
+
text.trim_start_matches("::")
|
|
182
|
+
.split("::")
|
|
183
|
+
.map(str::to_owned)
|
|
184
|
+
.collect(),
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// The names a definition is written inside, innermost last, with what its own path already spells.
|
|
189
|
+
fn enclosing_names(context: &RuleContext<'_>, node: Node<'_>, prefix: &[String]) -> Vec<String> {
|
|
190
|
+
let mut names: Vec<String> = Vec::new();
|
|
191
|
+
let mut current = node.parent();
|
|
192
|
+
while let Some(ancestor) = current {
|
|
193
|
+
if matches!(ancestor.kind_str(), "class" | "module")
|
|
194
|
+
&& let Some(defined) = defined_constant(context, ancestor)
|
|
195
|
+
{
|
|
196
|
+
for part in defined.into_iter().rev() {
|
|
197
|
+
names.push(part);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
current = ancestor.parent();
|
|
201
|
+
}
|
|
202
|
+
names.reverse();
|
|
203
|
+
names.extend(prefix.iter().cloned());
|
|
204
|
+
names
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// `match_acronym?`: an acronym listed in `AllowedAcronyms` may be written in full caps.
|
|
208
|
+
fn matches_acronym(expected: &str, name: &str, acronyms: &[String]) -> bool {
|
|
209
|
+
let folded = acronyms.iter().fold(name.to_owned(), |result, acronym| {
|
|
210
|
+
let mut capitalized = acronym.to_lowercase();
|
|
211
|
+
if let Some(first) = capitalized.get_mut(0..1) {
|
|
212
|
+
first.make_ascii_uppercase();
|
|
213
|
+
}
|
|
214
|
+
result.replace(acronym.as_str(), &capitalized)
|
|
215
|
+
});
|
|
216
|
+
expected == folded
|
|
217
|
+
}
|
|
218
|
+
|
|
50
219
|
/// `filename_good?`: the leading dot and the last extension are dropped, the one `+` an Action Pack
|
|
51
220
|
/// variant name carries becomes an underscore, and what is left has to match the pattern.
|
|
52
221
|
fn filename_good(basename: &str, pattern: &Regex) -> bool {
|
|
@@ -58,41 +227,3 @@ fn filename_good(basename: &str, pattern: &Regex) -> bool {
|
|
|
58
227
|
let stem = stem.replacen('+', "_", 1);
|
|
59
228
|
pattern.is_match(&stem)
|
|
60
229
|
}
|
|
61
|
-
|
|
62
|
-
/// `allowed_camel_case_file?`: a gemspec may use dashes to spell a namespace, and a file that only
|
|
63
|
-
/// reaches the run through an `Include` pattern written with capitals -- `Rakefile`, `Gemfile` --
|
|
64
|
-
/// is named after that pattern rather than after Ruby's conventions.
|
|
65
|
-
fn allowed_camel_case_file(context: &RuleContext<'_>, path: &Path) -> bool {
|
|
66
|
-
if path
|
|
67
|
-
.extension()
|
|
68
|
-
.is_some_and(|extension| extension == "gemspec")
|
|
69
|
-
{
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
let Some(includes) = context.setting_of::<Vec<String>>("AllCops", "Include") else {
|
|
73
|
-
return false;
|
|
74
|
-
};
|
|
75
|
-
let candidates: Vec<String> = [
|
|
76
|
-
path.to_str().map(str::to_owned),
|
|
77
|
-
path.file_name()
|
|
78
|
-
.and_then(|name| name.to_str())
|
|
79
|
-
.map(str::to_owned),
|
|
80
|
-
]
|
|
81
|
-
.into_iter()
|
|
82
|
-
.flatten()
|
|
83
|
-
.collect();
|
|
84
|
-
includes
|
|
85
|
-
.iter()
|
|
86
|
-
.filter(|pattern| {
|
|
87
|
-
pattern
|
|
88
|
-
.chars()
|
|
89
|
-
.any(|character| character.is_ascii_uppercase())
|
|
90
|
-
})
|
|
91
|
-
.filter_map(|pattern| Glob::new(pattern).ok())
|
|
92
|
-
.any(|glob| {
|
|
93
|
-
let matcher = glob.compile_matcher();
|
|
94
|
-
candidates
|
|
95
|
-
.iter()
|
|
96
|
-
.any(|candidate| matcher.is_match(candidate))
|
|
97
|
-
})
|
|
98
|
-
}
|
|
@@ -24,6 +24,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
24
24
|
.setting("EnforcedStyle")
|
|
25
25
|
.unwrap_or_else(|| "snake_case".to_owned()),
|
|
26
26
|
forbidden: context.setting("ForbiddenIdentifiers").unwrap_or_default(),
|
|
27
|
+
// `forbidden_name?` is `forbidden_identifier? || forbidden_pattern?`.
|
|
28
|
+
forbidden_patterns: super::support::forbidden_patterns(context),
|
|
27
29
|
reported: HashSet::new(),
|
|
28
30
|
offenses,
|
|
29
31
|
};
|
|
@@ -40,6 +42,7 @@ struct Check<'a, 'tree> {
|
|
|
40
42
|
context: &'a RuleContext<'tree>,
|
|
41
43
|
style: String,
|
|
42
44
|
forbidden: Vec<String>,
|
|
45
|
+
forbidden_patterns: Vec<&'static regex::Regex>,
|
|
43
46
|
/// `Base#add_offense` drops a second offense at a range it already reported, and
|
|
44
47
|
/// `attr_accessor :aB, :cD` reports both names over the same range.
|
|
45
48
|
reported: HashSet<Range<usize>>,
|
|
@@ -171,7 +174,7 @@ impl Check<'_, '_> {
|
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
fn class_emitter_method(&self, node: Node<'_>, name: &str) -> bool {
|
|
174
|
-
super::support::class_emitter_method(node, name, self.context
|
|
177
|
+
super::support::class_emitter_method(node, name, self.context)
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
/// Whether the call's receiver is the bare `Struct`/`Data` constant. `(const {nil? cbase} …)`
|
|
@@ -228,6 +231,10 @@ impl Check<'_, '_> {
|
|
|
228
231
|
|
|
229
232
|
fn is_forbidden(&self, name: &str) -> bool {
|
|
230
233
|
self.forbidden.iter().any(|forbidden| forbidden == name)
|
|
234
|
+
|| self
|
|
235
|
+
.forbidden_patterns
|
|
236
|
+
.iter()
|
|
237
|
+
.any(|pattern| pattern.is_match(name))
|
|
231
238
|
}
|
|
232
239
|
|
|
233
240
|
fn style_offense(&mut self, range: Range<usize>) {
|