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/lint/void.rs
CHANGED
|
@@ -4,7 +4,7 @@ use tree_sitter::Node;
|
|
|
4
4
|
|
|
5
5
|
use crate::diagnostic::{Edit, Offense};
|
|
6
6
|
use crate::rules::RuleContext;
|
|
7
|
-
use crate::rules::send_node::
|
|
7
|
+
use crate::rules::send_node::arguments;
|
|
8
8
|
use crate::rules::support;
|
|
9
9
|
|
|
10
10
|
use super::blocks::{BLOCK_KINDS, BlockArgs};
|
|
@@ -12,6 +12,7 @@ use super::literals::{is_literal, literal_type};
|
|
|
12
12
|
use super::locals::LocalVariables;
|
|
13
13
|
use super::statements::{Branch, begin_containers, body_children, has_clause, statements};
|
|
14
14
|
use crate::rules::node_ext::NodeExt;
|
|
15
|
+
use crate::rules::send_node::named_children_of;
|
|
15
16
|
|
|
16
17
|
const SELF_MSG: &str = "`self` used in void context.";
|
|
17
18
|
|
|
@@ -270,15 +271,26 @@ impl Void<'_, '_> {
|
|
|
270
271
|
.then(|| OperatorCall {
|
|
271
272
|
method,
|
|
272
273
|
selector: operator.byte_range(),
|
|
273
|
-
receiver: node
|
|
274
|
-
.field("left")
|
|
275
|
-
.map(|left| left.byte_range()),
|
|
274
|
+
receiver: node.field("left").map(|left| left.byte_range()),
|
|
276
275
|
argument_count: 1,
|
|
277
276
|
dot: None,
|
|
278
277
|
})
|
|
279
278
|
}
|
|
280
279
|
"unary" => {
|
|
281
280
|
let operator = node.field("operator")?;
|
|
281
|
+
// **A sign in front of a number is part of the number upstream.** `-3` parses to
|
|
282
|
+
// `(int -3)`, not to a `-@` send, so it is a literal here and no operator at all --
|
|
283
|
+
// the grammar keeps the two apart and would otherwise report both.
|
|
284
|
+
if matches!(self.text(operator), "-" | "+")
|
|
285
|
+
&& node.field("operand").is_some_and(|operand| {
|
|
286
|
+
matches!(
|
|
287
|
+
operand.kind_str(),
|
|
288
|
+
"integer" | "float" | "rational" | "complex"
|
|
289
|
+
)
|
|
290
|
+
})
|
|
291
|
+
{
|
|
292
|
+
return None;
|
|
293
|
+
}
|
|
282
294
|
let method = match self.text(operator) {
|
|
283
295
|
"-" => "-@",
|
|
284
296
|
"+" => "+@",
|
|
@@ -291,9 +303,7 @@ impl Void<'_, '_> {
|
|
|
291
303
|
Some(OperatorCall {
|
|
292
304
|
method,
|
|
293
305
|
selector: operator.byte_range(),
|
|
294
|
-
receiver: node
|
|
295
|
-
.field("operand")
|
|
296
|
-
.map(|operand| operand.byte_range()),
|
|
306
|
+
receiver: node.field("operand").map(|operand| operand.byte_range()),
|
|
297
307
|
argument_count: 0,
|
|
298
308
|
dot: None,
|
|
299
309
|
})
|
|
@@ -309,13 +319,9 @@ impl Void<'_, '_> {
|
|
|
309
319
|
Some(OperatorCall {
|
|
310
320
|
method: name,
|
|
311
321
|
selector: method.byte_range(),
|
|
312
|
-
receiver: node
|
|
313
|
-
.field("receiver")
|
|
314
|
-
.map(|receiver| receiver.byte_range()),
|
|
322
|
+
receiver: node.field("receiver").map(|receiver| receiver.byte_range()),
|
|
315
323
|
argument_count: arguments(node).len(),
|
|
316
|
-
dot: node
|
|
317
|
-
.field("operator")
|
|
318
|
-
.map(|operator| operator.byte_range()),
|
|
324
|
+
dot: node.field("operator").map(|operator| operator.byte_range()),
|
|
319
325
|
})
|
|
320
326
|
}
|
|
321
327
|
_ => None,
|
|
@@ -336,7 +342,7 @@ impl Void<'_, '_> {
|
|
|
336
342
|
return;
|
|
337
343
|
}
|
|
338
344
|
if matches!(expression.kind_str(), "case" | "case_match") {
|
|
339
|
-
for child in
|
|
345
|
+
for child in named_children_of(expression, self.context) {
|
|
340
346
|
match child.kind_str() {
|
|
341
347
|
"when" | "in_clause" => {
|
|
342
348
|
if let Branch::One(body) = Branch::of(child.field("body")) {
|
|
@@ -387,7 +393,11 @@ impl Void<'_, '_> {
|
|
|
387
393
|
node.kind_str(),
|
|
388
394
|
"instance_variable" | "global_variable" | "class_variable"
|
|
389
395
|
) || (node.kind_str() == "identifier"
|
|
390
|
-
&& (self.locals.is_lvar(node)
|
|
396
|
+
&& (self.locals.is_lvar(node)
|
|
397
|
+
|| self.is_block_parameter(node)
|
|
398
|
+
// `special_keyword?`: upstream parses these as `const` and reports them with the
|
|
399
|
+
// variable message. The grammar spells them as plain identifiers.
|
|
400
|
+
|| matches!(self.text(node), "__FILE__" | "__LINE__" | "__ENCODING__")));
|
|
391
401
|
if !constant && !variable {
|
|
392
402
|
return;
|
|
393
403
|
}
|
|
@@ -406,8 +416,9 @@ impl Void<'_, '_> {
|
|
|
406
416
|
while let Some(ancestor) = current {
|
|
407
417
|
if BLOCK_KINDS.contains(&ancestor.kind_str()) {
|
|
408
418
|
return match BlockArgs::of(ancestor, self.context, &self.locals) {
|
|
409
|
-
BlockArgs::Numbered(highest) =>
|
|
410
|
-
.any(|index| self.text(node) == format!("_{index}"))
|
|
419
|
+
BlockArgs::Numbered(highest) => {
|
|
420
|
+
(1..=highest).any(|index| self.text(node) == format!("_{index}"))
|
|
421
|
+
}
|
|
411
422
|
BlockArgs::It => self.text(node) == "it",
|
|
412
423
|
BlockArgs::Written(_) => false,
|
|
413
424
|
};
|
|
@@ -501,7 +512,8 @@ impl Void<'_, '_> {
|
|
|
501
512
|
if parent.is_some_and(|parent| {
|
|
502
513
|
matches!(
|
|
503
514
|
parent.kind_str(),
|
|
504
|
-
"if" | "elsif"
|
|
515
|
+
"if" | "elsif"
|
|
516
|
+
| "unless"
|
|
505
517
|
| "if_modifier"
|
|
506
518
|
| "unless_modifier"
|
|
507
519
|
| "conditional"
|
|
@@ -549,10 +561,10 @@ impl Void<'_, '_> {
|
|
|
549
561
|
/// `entirely_literal?`.
|
|
550
562
|
fn entirely_literal(&self, node: Node<'_>) -> bool {
|
|
551
563
|
match node.kind_str() {
|
|
552
|
-
"array" | "string_array" | "symbol_array" =>
|
|
564
|
+
"array" | "string_array" | "symbol_array" => named_children_of(node, self.context)
|
|
553
565
|
.into_iter()
|
|
554
566
|
.all(|child| self.entirely_literal(child)),
|
|
555
|
-
"hash" =>
|
|
567
|
+
"hash" => named_children_of(node, self.context)
|
|
556
568
|
.into_iter()
|
|
557
569
|
.filter(|child| child.kind_str() == "pair")
|
|
558
570
|
.all(|pair| {
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
//! `Metrics/AbcSize`, a port of `Utils::AbcSizeCalculator`.
|
|
2
2
|
//!
|
|
3
|
-
//! `CountRepeatedAttributes` is
|
|
4
|
-
//!
|
|
5
|
-
|
|
3
|
+
//! `CountRepeatedAttributes` is `true` in the bundled configuration, which turns
|
|
4
|
+
//! `Utils::RepeatedAttributeDiscount` off; [`Attributes`] is what a run setting it to `false` gets.
|
|
5
|
+
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
|
|
8
|
+
use tree_sitter::Node;
|
|
6
9
|
|
|
7
10
|
use super::complexity::{Allowed, CsendDiscount, Emit, Kind, Order, Walk, measured};
|
|
8
11
|
use crate::diagnostic::Offense;
|
|
9
12
|
use crate::rules::RuleContext;
|
|
13
|
+
use crate::rules::node_ext::NodeExt;
|
|
10
14
|
|
|
11
15
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
12
16
|
let max = context
|
|
@@ -21,8 +25,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
21
25
|
let fragments = context.fragments();
|
|
22
26
|
let locals = context.metric_locals();
|
|
23
27
|
let walk = Walk::new(context, locals, fragments, Order::Post);
|
|
28
|
+
// `discount_repeated_attributes: !cop_config['CountRepeatedAttributes']`.
|
|
29
|
+
let discounting = !context
|
|
30
|
+
.setting::<bool>("CountRepeatedAttributes")
|
|
31
|
+
.unwrap_or(true);
|
|
24
32
|
for method in methods {
|
|
25
|
-
let vector = Vector::of(&walk, method.body);
|
|
33
|
+
let vector = Vector::of(&walk, method.body, context, locals, discounting);
|
|
26
34
|
let size = vector.size();
|
|
27
35
|
if size <= max {
|
|
28
36
|
continue;
|
|
@@ -49,20 +57,47 @@ struct Vector {
|
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
impl Vector {
|
|
52
|
-
fn of(
|
|
60
|
+
fn of(
|
|
61
|
+
walk: &Walk<'_>,
|
|
62
|
+
body: tree_sitter::Node<'_>,
|
|
63
|
+
context: &RuleContext<'_>,
|
|
64
|
+
locals: &super::locals::Locals,
|
|
65
|
+
discounting: bool,
|
|
66
|
+
) -> Self {
|
|
53
67
|
let mut vector = Self::default();
|
|
54
68
|
let mut discount = CsendDiscount::default();
|
|
55
|
-
|
|
69
|
+
let mut attributes = discounting.then(Attributes::default);
|
|
70
|
+
walk.run(body, &mut |emit| {
|
|
71
|
+
vector.count(emit, &mut discount, context, locals, attributes.as_mut());
|
|
72
|
+
});
|
|
56
73
|
vector
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
/// `AbcSizeCalculator#calculate_node`, whose two arms are exclusive: a call is never also
|
|
60
77
|
/// counted as a condition, though it may add one.
|
|
61
|
-
fn count<'a>(
|
|
78
|
+
fn count<'a>(
|
|
79
|
+
&mut self,
|
|
80
|
+
emit: Emit<'a>,
|
|
81
|
+
discount: &mut CsendDiscount<'a>,
|
|
82
|
+
context: &RuleContext<'_>,
|
|
83
|
+
locals: &super::locals::Locals,
|
|
84
|
+
attributes: Option<&mut Attributes>,
|
|
85
|
+
) {
|
|
86
|
+
// `RepeatedAttributeDiscount#calculate_node` invalidates before the count, so an
|
|
87
|
+
// assignment to a receiver does not discount the reads that follow it.
|
|
88
|
+
let repeated = match attributes {
|
|
89
|
+
Some(known) => {
|
|
90
|
+
known.invalidate(emit, context, locals);
|
|
91
|
+
matches!(emit.kind, Kind::Send | Kind::Csend)
|
|
92
|
+
&& known.repeats(emit.node, context, locals)
|
|
93
|
+
}
|
|
94
|
+
None => false,
|
|
95
|
+
};
|
|
62
96
|
if self.assignment(emit, discount) {
|
|
63
97
|
self.assignment += 1;
|
|
64
98
|
}
|
|
65
99
|
match emit.kind {
|
|
100
|
+
Kind::Send | Kind::Csend if repeated => {}
|
|
66
101
|
Kind::Send | Kind::Csend | Kind::Yield => self.branch_node(emit, discount),
|
|
67
102
|
// A block on a method that is known not to iterate is not a decision point.
|
|
68
103
|
kind if is_condition(kind) && emit.iterating != Some(false) => {
|
|
@@ -275,3 +310,158 @@ fn scaled(value: u128, twos: i32, tens: i32) -> Option<u128> {
|
|
|
275
310
|
let value = value.checked_mul(2u128.checked_pow(u32::try_from(twos).ok()?)?)?;
|
|
276
311
|
value.checked_mul(10u128.checked_pow(u32::try_from(tens).ok()?)?)
|
|
277
312
|
}
|
|
313
|
+
|
|
314
|
+
/// `Utils::RepeatedAttributeDiscount`: the attribute reads already seen, so a second read of the
|
|
315
|
+
/// same one is not counted as another branch.
|
|
316
|
+
///
|
|
317
|
+
/// Upstream keys the tree by the receiver node itself and makes the `nil` and `self` receivers
|
|
318
|
+
/// share one entry, which is what lets `bar`, `self.bar` and `bar` count once between them. The
|
|
319
|
+
/// keys here spell the same identity as a string.
|
|
320
|
+
#[derive(Default)]
|
|
321
|
+
struct Attributes {
|
|
322
|
+
tree: HashMap<String, Attributes>,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
impl Attributes {
|
|
326
|
+
/// `discount_repeated_attribute?`: walks the receiver chain, recording what it has not seen.
|
|
327
|
+
/// True only when every step of the chain was already there.
|
|
328
|
+
fn repeats(
|
|
329
|
+
&mut self,
|
|
330
|
+
node: Node<'_>,
|
|
331
|
+
context: &RuleContext<'_>,
|
|
332
|
+
locals: &super::locals::Locals,
|
|
333
|
+
) -> bool {
|
|
334
|
+
match key_path(node, context, locals) {
|
|
335
|
+
Some(path) => self.record(&path),
|
|
336
|
+
None => false,
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fn record(&mut self, path: &[String]) -> bool {
|
|
341
|
+
let Some((head, rest)) = path.split_first() else {
|
|
342
|
+
return true;
|
|
343
|
+
};
|
|
344
|
+
let known = self.tree.contains_key(head);
|
|
345
|
+
let deeper = self.tree.entry(head.clone()).or_default().record(rest);
|
|
346
|
+
known & deeper
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// `update_repeated_attribute`: writing to a receiver invalidates what was read through it.
|
|
350
|
+
fn invalidate(
|
|
351
|
+
&mut self,
|
|
352
|
+
emit: Emit<'_>,
|
|
353
|
+
context: &RuleContext<'_>,
|
|
354
|
+
locals: &super::locals::Locals,
|
|
355
|
+
) {
|
|
356
|
+
let Some((path, method)) = setter_to_getter(emit, context, locals) else {
|
|
357
|
+
return;
|
|
358
|
+
};
|
|
359
|
+
let Some(branch) = self.navigate(&path) else {
|
|
360
|
+
return;
|
|
361
|
+
};
|
|
362
|
+
match method {
|
|
363
|
+
Some(method) => drop(branch.tree.remove(&method)),
|
|
364
|
+
None => branch.tree.clear(),
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
fn navigate(&mut self, path: &[String]) -> Option<&mut Self> {
|
|
369
|
+
let mut current = self;
|
|
370
|
+
for key in path {
|
|
371
|
+
current = current.tree.get_mut(key)?;
|
|
372
|
+
}
|
|
373
|
+
Some(current)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/// `setter_to_getter`, as the receiver chain to invalidate and the one attribute under it -- or
|
|
378
|
+
/// the whole branch, when a variable itself was written.
|
|
379
|
+
fn setter_to_getter(
|
|
380
|
+
emit: Emit<'_>,
|
|
381
|
+
context: &RuleContext<'_>,
|
|
382
|
+
locals: &super::locals::Locals,
|
|
383
|
+
) -> Option<(Vec<String>, Option<String>)> {
|
|
384
|
+
match emit.kind {
|
|
385
|
+
// `(lvasgn :my_var _)` and its three siblings: everything read through the name goes.
|
|
386
|
+
Kind::Lvasgn | Kind::Asgn => {
|
|
387
|
+
let target = emit.node.field("left")?;
|
|
388
|
+
Some((vec![root_key(target, context, locals)?], None))
|
|
389
|
+
}
|
|
390
|
+
// `foo.bar = 1` and `foo.bar ||= 1` both name one attribute of one receiver.
|
|
391
|
+
Kind::Send | Kind::Csend if emit.setter => {
|
|
392
|
+
let receiver = emit.node.field("receiver");
|
|
393
|
+
let method = context.source.node_text(emit.node.field("method")?);
|
|
394
|
+
let path = match receiver {
|
|
395
|
+
Some(receiver) => key_path(receiver, context, locals)?,
|
|
396
|
+
None => vec![ROOT.to_owned()],
|
|
397
|
+
};
|
|
398
|
+
Some((path, Some(method.trim_end_matches('=').to_owned())))
|
|
399
|
+
}
|
|
400
|
+
Kind::OpAsgn | Kind::OrAsgn | Kind::AndAsgn => {
|
|
401
|
+
let target = emit.node.field("left")?;
|
|
402
|
+
match target.kind_str() {
|
|
403
|
+
"call" => {
|
|
404
|
+
let method = context.source.node_text(target.field("method")?);
|
|
405
|
+
let path = match target.field("receiver") {
|
|
406
|
+
Some(receiver) => key_path(receiver, context, locals)?,
|
|
407
|
+
None => vec![ROOT.to_owned()],
|
|
408
|
+
};
|
|
409
|
+
Some((path, Some(method.to_owned())))
|
|
410
|
+
}
|
|
411
|
+
_ => Some((vec![root_key(target, context, locals)?], None)),
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
_ => None,
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/// The entry `nil` and `self` share upstream, by sharing one hash between the two keys.
|
|
419
|
+
const ROOT: &str = "self";
|
|
420
|
+
|
|
421
|
+
/// `find_attributes`, flattened: the chain of names a read walks through, or `None` when the node
|
|
422
|
+
/// is not a series of argument-less calls over a root the tree can key on.
|
|
423
|
+
fn key_path(
|
|
424
|
+
node: Node<'_>,
|
|
425
|
+
context: &RuleContext<'_>,
|
|
426
|
+
locals: &super::locals::Locals,
|
|
427
|
+
) -> Option<Vec<String>> {
|
|
428
|
+
if let Some(root) = root_key(node, context, locals) {
|
|
429
|
+
return Some(vec![root]);
|
|
430
|
+
}
|
|
431
|
+
match node.kind_str() {
|
|
432
|
+
// `attribute_call?` is `(call _receiver _method)` -- two children, so no arguments.
|
|
433
|
+
"call" if node.field("arguments").is_none() && node.field("block").is_none() => {
|
|
434
|
+
let method = context.source.node_text(node.field("method")?);
|
|
435
|
+
let mut path = match node.field("receiver") {
|
|
436
|
+
Some(receiver) => key_path(receiver, context, locals)?,
|
|
437
|
+
None => vec![ROOT.to_owned()],
|
|
438
|
+
};
|
|
439
|
+
path.push(method.to_owned());
|
|
440
|
+
Some(path)
|
|
441
|
+
}
|
|
442
|
+
// A bare name that is not a local is `(send nil :name)`.
|
|
443
|
+
"identifier" => Some(vec![
|
|
444
|
+
ROOT.to_owned(),
|
|
445
|
+
context.source.node_text(node).to_owned(),
|
|
446
|
+
]),
|
|
447
|
+
_ => None,
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/// `root_node?`: the receivers the tree keys on directly rather than walking through.
|
|
452
|
+
fn root_key(
|
|
453
|
+
node: Node<'_>,
|
|
454
|
+
context: &RuleContext<'_>,
|
|
455
|
+
locals: &super::locals::Locals,
|
|
456
|
+
) -> Option<String> {
|
|
457
|
+
let text = || context.source.node_text(node);
|
|
458
|
+
match node.kind_str() {
|
|
459
|
+
"self" => Some(ROOT.to_owned()),
|
|
460
|
+
"identifier" if locals.is_lvar(node) => Some(format!("lvar:{}", text())),
|
|
461
|
+
"instance_variable" => Some(format!("ivar:{}", text())),
|
|
462
|
+
"class_variable" => Some(format!("cvar:{}", text())),
|
|
463
|
+
"global_variable" => Some(format!("gvar:{}", text())),
|
|
464
|
+
"constant" | "scope_resolution" => Some(format!("const:{}", text())),
|
|
465
|
+
_ => None,
|
|
466
|
+
}
|
|
467
|
+
}
|
|
@@ -7,10 +7,17 @@ use crate::rules::node_ext::NodeExt;
|
|
|
7
7
|
|
|
8
8
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
9
9
|
let max: usize = context.setting("Max").unwrap_or(25);
|
|
10
|
-
let allowed
|
|
10
|
+
let allowed = crate::rules::support::allowed_methods(context);
|
|
11
|
+
// `matches_allowed_pattern?(node.method_name)`: the patterns are matched against the method
|
|
12
|
+
// name alone, never against the receiver.
|
|
13
|
+
let patterns = crate::rules::naming::support::forbidden_patterns_named(context, "AllowedPatterns");
|
|
11
14
|
let heredocs = HeredocEnds::new(context);
|
|
12
15
|
for node in context.nodes_of_any(&["block", "do_block"]) {
|
|
13
|
-
if block_method_allowed(node, context, &allowed)
|
|
16
|
+
if block_method_allowed(node, context, &allowed)
|
|
17
|
+
|| block_method_name(node, context)
|
|
18
|
+
.is_some_and(|name| patterns.iter().any(|pattern| pattern.is_match(name)))
|
|
19
|
+
|| class_constructor(node, context)
|
|
20
|
+
{
|
|
14
21
|
continue;
|
|
15
22
|
}
|
|
16
23
|
report_length(
|
|
@@ -40,13 +47,15 @@ fn block_method_name<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> Option
|
|
|
40
47
|
)
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
/// `node.receiver&.source&.gsub(/\s+/, '')`: the receiver as written, with every run of
|
|
51
|
+
/// whitespace removed -- a chain broken over lines (`Foo::\n Bar.baz`) names the same receiver
|
|
52
|
+
/// as the one written on a single line.
|
|
53
|
+
fn block_receiver(node: Node<'_>, context: &RuleContext<'_>) -> Option<String> {
|
|
54
|
+
let call = node
|
|
55
|
+
.parent_of(context)
|
|
56
|
+
.filter(|parent| parent.kind_str() == "call")?;
|
|
57
|
+
let written = context.source.node_text(call.field("receiver")?);
|
|
58
|
+
Some(written.split_whitespace().collect())
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
/// `AllowedMethods` entries are either a bare method name or `Receiver.method`; the second form
|
|
@@ -58,7 +67,7 @@ fn block_method_allowed(node: Node<'_>, context: &RuleContext<'_>, allowed: &[St
|
|
|
58
67
|
let receiver = block_receiver(node, context);
|
|
59
68
|
allowed.iter().any(|entry| match entry.split_once('.') {
|
|
60
69
|
Some((entry_receiver, entry_method)) => {
|
|
61
|
-
entry_method == method &&
|
|
70
|
+
entry_method == method && receiver.as_deref() == Some(entry_receiver)
|
|
62
71
|
}
|
|
63
72
|
None => entry == method,
|
|
64
73
|
})
|
|
@@ -127,8 +127,7 @@ fn clause_range(node: Node<'_>) -> Range<usize> {
|
|
|
127
127
|
/// The `resbody` of `x rescue y`, which starts at the keyword and ends with the handler.
|
|
128
128
|
fn modifier_clause(node: Node<'_>, handler: Node<'_>) -> Range<usize> {
|
|
129
129
|
let mut cursor = node.walk();
|
|
130
|
-
let keyword = node
|
|
131
|
-
.children(&mut cursor)
|
|
130
|
+
let keyword = node.children(&mut cursor)
|
|
132
131
|
.find(|child| !child.is_named() && child.kind_str() == "rescue")
|
|
133
132
|
.map_or(node.start_byte(), |child| child.start_byte());
|
|
134
133
|
keyword..handler.end_byte()
|
|
@@ -8,7 +8,12 @@ use crate::rules::node_ext::NodeExt;
|
|
|
8
8
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
9
9
|
let max: usize = context.setting("Max").unwrap_or(100);
|
|
10
10
|
let heredocs = HeredocEnds::new(context);
|
|
11
|
-
for node in context.nodes_of_any(&[
|
|
11
|
+
for node in context.nodes_of_any(&[
|
|
12
|
+
"class",
|
|
13
|
+
"singleton_class",
|
|
14
|
+
"assignment",
|
|
15
|
+
"operator_assignment",
|
|
16
|
+
]) {
|
|
12
17
|
let (measured, target) = match node.kind_str() {
|
|
13
18
|
// A `class << self` inside a class body is part of that class's length rather than a
|
|
14
19
|
// class of its own. Only `class` and `module` are classlike to `CodeLengthCalculator`,
|
|
@@ -17,8 +22,10 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
17
22
|
"class" => (node, LengthTarget::Classlike),
|
|
18
23
|
// `CONST = Class.new { ... }` and `CONST = Struct.new(...) { ... }` are class
|
|
19
24
|
// definitions written as expressions, which `Node#class_definition?` recognises and
|
|
20
|
-
// `on_casgn` measures here rather than as blocks.
|
|
21
|
-
|
|
25
|
+
// `on_casgn` measures here rather than as blocks. `CONST ||= Struct.new { ... }`
|
|
26
|
+
// reaches upstream the same way: the `casgn` has no expression of its own, so
|
|
27
|
+
// `find_expression_within_parent` reads the assignment wrapping it.
|
|
28
|
+
"assignment" | "operator_assignment" => match class_definition_block(context, node) {
|
|
22
29
|
Some(block) => (block, LengthTarget::Block),
|
|
23
30
|
None => continue,
|
|
24
31
|
},
|
|
@@ -46,7 +53,12 @@ fn class_definition_block<'tree>(
|
|
|
46
53
|
assignment: Node<'tree>,
|
|
47
54
|
) -> Option<Node<'tree>> {
|
|
48
55
|
let target = assignment.field("left")?;
|
|
49
|
-
|
|
56
|
+
// `Foo, Bar = Struct.new { ... }`: the `casgn` sits inside an `masgn` upstream, which
|
|
57
|
+
// `find_expression_within_parent` reads through.
|
|
58
|
+
if !matches!(
|
|
59
|
+
target.kind_str(),
|
|
60
|
+
"constant" | "scope_resolution" | "left_assignment_list"
|
|
61
|
+
) {
|
|
50
62
|
return None;
|
|
51
63
|
}
|
|
52
64
|
let call = assignment
|
|
@@ -6,6 +6,7 @@ use crate::diagnostic::Offense;
|
|
|
6
6
|
use crate::rules::RuleContext;
|
|
7
7
|
use crate::rules::node_ext::NodeExt;
|
|
8
8
|
use crate::rules::send_node::{named_children, top_level_constant};
|
|
9
|
+
use crate::rules::send_node::named_children_of;
|
|
9
10
|
|
|
10
11
|
const MSG: &str = "Avoid hard coding large quantities of data in code. \
|
|
11
12
|
Prefer reading the data from an external source.";
|
|
@@ -46,7 +47,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
46
47
|
if !top_level_constant(object, "Set", context) || is_assignment_target(node) {
|
|
47
48
|
continue;
|
|
48
49
|
}
|
|
49
|
-
let children =
|
|
50
|
+
let children = named_children_of(node, context);
|
|
50
51
|
let indices = children.get(1..).unwrap_or_default();
|
|
51
52
|
if fold_pairs(indices).len() >= threshold {
|
|
52
53
|
offenses.push(context.offense(MSG, node.byte_range()));
|
|
@@ -108,6 +108,9 @@ pub(super) enum Order {
|
|
|
108
108
|
|
|
109
109
|
pub(super) struct Walk<'a> {
|
|
110
110
|
source: &'a SourceFile,
|
|
111
|
+
/// The file's node index. The walk asks for a parent on every identifier it meets, and
|
|
112
|
+
/// `Node::parent` walks down from the root each time.
|
|
113
|
+
index: &'a super::super::AstIndex<'a>,
|
|
111
114
|
locals: &'a Locals,
|
|
112
115
|
fragments: &'a Fragments,
|
|
113
116
|
order: Order,
|
|
@@ -122,6 +125,7 @@ impl<'a> Walk<'a> {
|
|
|
122
125
|
) -> Self {
|
|
123
126
|
Self {
|
|
124
127
|
source: context.source,
|
|
128
|
+
index: context.ast_index(),
|
|
125
129
|
locals,
|
|
126
130
|
fragments,
|
|
127
131
|
order,
|
|
@@ -137,8 +141,18 @@ impl<'a> Walk<'a> {
|
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
fn children<F: FnMut(Emit<'a>)>(&self, node: Node<'a>, sink: &mut F) {
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
let mut cursor = node.walk();
|
|
145
|
+
if !cursor.goto_first_child() {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
loop {
|
|
149
|
+
let child = cursor.node();
|
|
150
|
+
if child.is_named() {
|
|
151
|
+
self.visit(child, sink);
|
|
152
|
+
}
|
|
153
|
+
if !cursor.goto_next_sibling() {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
142
156
|
}
|
|
143
157
|
}
|
|
144
158
|
|
|
@@ -694,9 +708,10 @@ impl<'a> Walk<'a> {
|
|
|
694
708
|
|
|
695
709
|
fn visit_block_argument<F: FnMut(Emit<'a>)>(&self, node: Node<'a>, sink: &mut F) {
|
|
696
710
|
let mut emit = Emit::of(Kind::BlockPass, node);
|
|
697
|
-
emit.iterating =
|
|
698
|
-
.
|
|
699
|
-
.
|
|
711
|
+
emit.iterating = self
|
|
712
|
+
.index
|
|
713
|
+
.parent(node)
|
|
714
|
+
.and_then(|arguments| self.index.parent(arguments))
|
|
700
715
|
.filter(|call| call.kind_str() == "call")
|
|
701
716
|
.and_then(|call| self.iterating_call(call));
|
|
702
717
|
self.around(emit, sink, |walk, sink| walk.children(node, sink));
|
|
@@ -819,9 +834,32 @@ impl<'a> Walk<'a> {
|
|
|
819
834
|
|
|
820
835
|
/// Whether the identifier stands where the parser would have built `(send nil :name)`.
|
|
821
836
|
fn receiverless_call(&self, node: Node<'_>) -> bool {
|
|
822
|
-
is_receiverless_call(node)
|
|
837
|
+
is_receiverless_call(node, self.index)
|
|
823
838
|
&& !self.locals.is_lvar(node)
|
|
824
839
|
&& !is_keyword_literal(self.text(node))
|
|
840
|
+
&& !self.is_implicit_block_parameter(node)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/// `it` inside a block that declares no parameters is that block's parameter -- an `itblock`
|
|
844
|
+
/// upstream, where the name is a variable rather than a call.
|
|
845
|
+
fn is_implicit_block_parameter(&self, node: Node<'_>) -> bool {
|
|
846
|
+
if self.text(node) != "it" {
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
let mut current = self.index.parent(node);
|
|
850
|
+
while let Some(parent) = current {
|
|
851
|
+
if matches!(parent.kind_str(), "block" | "do_block") {
|
|
852
|
+
return parent.field("parameters").is_none();
|
|
853
|
+
}
|
|
854
|
+
if matches!(
|
|
855
|
+
parent.kind_str(),
|
|
856
|
+
"method" | "singleton_method" | "class" | "module" | "lambda"
|
|
857
|
+
) {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
current = self.index.parent(parent);
|
|
861
|
+
}
|
|
862
|
+
false
|
|
825
863
|
}
|
|
826
864
|
|
|
827
865
|
/// Whether a block takes its parameters implicitly through `_1`, which makes it a `numblock`
|
|
@@ -986,7 +1024,7 @@ pub(super) struct Allowed {
|
|
|
986
1024
|
|
|
987
1025
|
impl Allowed {
|
|
988
1026
|
pub(super) fn new(context: &RuleContext<'_>) -> Self {
|
|
989
|
-
let methods
|
|
1027
|
+
let methods = crate::rules::support::allowed_methods(context);
|
|
990
1028
|
let patterns: Vec<String> = context.setting("AllowedPatterns").unwrap_or_default();
|
|
991
1029
|
Self {
|
|
992
1030
|
methods,
|
|
@@ -1058,14 +1096,14 @@ fn collect_targets<'a>(node: Node<'a>, targets: &mut Vec<Node<'a>>) {
|
|
|
1058
1096
|
|
|
1059
1097
|
/// Whether the identifier stands where the parser would have built `(send nil :name)` rather than
|
|
1060
1098
|
/// a name being declared, written or called on something else.
|
|
1061
|
-
fn is_receiverless_call(node: Node<'_>) -> bool {
|
|
1062
|
-
let Some(parent) =
|
|
1099
|
+
fn is_receiverless_call(node: Node<'_>, index: &super::super::AstIndex<'_>) -> bool {
|
|
1100
|
+
let Some(parent) = index.parent(node) else {
|
|
1063
1101
|
return true;
|
|
1064
1102
|
};
|
|
1065
1103
|
match parent.kind_str() {
|
|
1066
|
-
"call" => field_name(node, parent) != Some("method"),
|
|
1067
|
-
"method" | "singleton_method" => field_name(node, parent) != Some("name"),
|
|
1068
|
-
"assignment" | "operator_assignment" => field_name(node, parent) != Some("left"),
|
|
1104
|
+
"call" => field_name(node, parent, index) != Some("method"),
|
|
1105
|
+
"method" | "singleton_method" => field_name(node, parent, index) != Some("name"),
|
|
1106
|
+
"assignment" | "operator_assignment" => field_name(node, parent, index) != Some("left"),
|
|
1069
1107
|
"left_assignment_list"
|
|
1070
1108
|
| "rest_assignment"
|
|
1071
1109
|
| "destructured_left_assignment"
|
|
@@ -1081,8 +1119,8 @@ fn is_receiverless_call(node: Node<'_>) -> bool {
|
|
|
1081
1119
|
| "keyword_parameter"
|
|
1082
1120
|
| "splat_parameter"
|
|
1083
1121
|
| "hash_splat_parameter"
|
|
1084
|
-
| "block_parameter" => field_name(node, parent) != Some("name"),
|
|
1085
|
-
"for" => field_name(node, parent) != Some("pattern"),
|
|
1122
|
+
| "block_parameter" => field_name(node, parent, index) != Some("name"),
|
|
1123
|
+
"for" => field_name(node, parent, index) != Some("pattern"),
|
|
1086
1124
|
_ => true,
|
|
1087
1125
|
}
|
|
1088
1126
|
}
|