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
|
@@ -11,6 +11,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
11
11
|
.unwrap_or_default();
|
|
12
12
|
for range in context.comment_ranges() {
|
|
13
13
|
let range = super::comments::parser_range(range, context);
|
|
14
|
+
// The parser's span for a block comment can run past a file with no final newline.
|
|
15
|
+
let range = range.start..range.end.min(context.source.text().len());
|
|
14
16
|
let comment = &context.source.text()[range.clone()];
|
|
15
17
|
if comment.is_ascii() {
|
|
16
18
|
continue;
|
data/src/rules/style/attr.rs
CHANGED
|
@@ -97,7 +97,7 @@ fn is_class_eval(context: &RuleContext<'_>, block: Node<'_>) -> bool {
|
|
|
97
97
|
|
|
98
98
|
/// `each_descendant(:def).any? { |def_node| def_node.method?(:attr) }`.
|
|
99
99
|
fn defines_attr(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
100
|
-
super::conditional::descendants(node)
|
|
100
|
+
super::conditional::descendants(node, context)
|
|
101
101
|
.into_iter()
|
|
102
102
|
.filter(|descendant| descendant.kind_str() == "method")
|
|
103
103
|
.any(|definition| {
|
|
@@ -61,7 +61,7 @@ fn cleans_up(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
|
61
61
|
return false;
|
|
62
62
|
};
|
|
63
63
|
if parent.kind_str() == "program" {
|
|
64
|
-
let statements: Vec<Node<'_>> = super::nodes::
|
|
64
|
+
let statements: Vec<Node<'_>> = super::nodes::children_in(parent, context)
|
|
65
65
|
.into_iter()
|
|
66
66
|
.filter(|child| child.kind_str() != "comment")
|
|
67
67
|
.collect();
|
|
@@ -95,12 +95,12 @@ fn comparison<'tree>(
|
|
|
95
95
|
/// `(begin (send _ :& _))`: the two operands of a parenthesized mask.
|
|
96
96
|
fn masked<'tree>(
|
|
97
97
|
node: Node<'tree>,
|
|
98
|
-
context: &RuleContext<'_>,
|
|
98
|
+
context: &'tree RuleContext<'_>,
|
|
99
99
|
) -> Option<(Node<'tree>, Node<'tree>)> {
|
|
100
100
|
if node.kind_str() != "parenthesized_statements" {
|
|
101
101
|
return None;
|
|
102
102
|
}
|
|
103
|
-
let inner = super::nodes::
|
|
103
|
+
let inner = super::nodes::children_in(node, context);
|
|
104
104
|
let [inner] = inner.as_slice() else {
|
|
105
105
|
return None;
|
|
106
106
|
};
|
|
@@ -36,7 +36,7 @@ pub(super) fn implicit(context: &RuleContext<'_>, block: Node<'_>) -> bool {
|
|
|
36
36
|
/// The part of the body that belongs to this block: a nested block's `_1` is that block's, not
|
|
37
37
|
/// this one's.
|
|
38
38
|
fn scan(context: &RuleContext<'_>, node: Node<'_>, numbered: &mut bool, it: &mut bool) {
|
|
39
|
-
for child in super::nodes::
|
|
39
|
+
for child in super::nodes::children_in(node, context) {
|
|
40
40
|
if matches!(child.kind_str(), "block" | "do_block" | "lambda") {
|
|
41
41
|
continue;
|
|
42
42
|
}
|
|
@@ -18,9 +18,18 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
18
18
|
// the grammar stops at the last character of that line.
|
|
19
19
|
let start = node.start_byte();
|
|
20
20
|
let mut end = node.end_byte();
|
|
21
|
-
|
|
21
|
+
// **A file that does not end in a newline still has one to the parser**, and a block
|
|
22
|
+
// comment's range runs one character past even that. Only the report reaches there; every
|
|
23
|
+
// read below stays inside the text.
|
|
24
|
+
let closed_by_newline = text.as_bytes().get(end) == Some(&b'\n');
|
|
25
|
+
if closed_by_newline {
|
|
22
26
|
end += 1;
|
|
23
27
|
}
|
|
28
|
+
let reported_end = match closed_by_newline {
|
|
29
|
+
true => end,
|
|
30
|
+
false => end + 2,
|
|
31
|
+
};
|
|
32
|
+
|
|
24
33
|
// `=begin\n=end` is the shortest block comment the parser accepts; anything shorter would
|
|
25
34
|
// only reach here from a file that does not parse, where no cop runs at all.
|
|
26
35
|
if end - start < BEGIN_LENGTH + 4 {
|
|
@@ -61,7 +70,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
61
70
|
replacement: String::new(),
|
|
62
71
|
safe: true,
|
|
63
72
|
});
|
|
64
|
-
offenses.push(context.offense(MSG, start..
|
|
73
|
+
offenses.push(context.offense(MSG, start..reported_end).corrected_by_all(edits));
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
|
|
@@ -7,6 +7,7 @@ use tree_sitter::Node;
|
|
|
7
7
|
use crate::diagnostic::{Edit, Offense};
|
|
8
8
|
use crate::rules::RuleContext;
|
|
9
9
|
use crate::rules::node_ext::NodeExt;
|
|
10
|
+
use crate::rules::send_node::all_children_iter;
|
|
10
11
|
|
|
11
12
|
const MSG_MULTILINE: &str = "Avoid using `{...}` for multi-line blocks.";
|
|
12
13
|
const MSG_SINGLE_LINE: &str = "Prefer `{...}` over `do...end` for single-line blocks.";
|
|
@@ -26,6 +27,32 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
26
27
|
.setting::<String>("EnforcedStyle")
|
|
27
28
|
.unwrap_or_else(|| "line_count_based".to_owned()),
|
|
28
29
|
braces_required_methods: context.setting("BracesRequiredMethods").unwrap_or_default(),
|
|
30
|
+
functional_methods: context.setting("FunctionalMethods").unwrap_or_else(|| {
|
|
31
|
+
["let", "let!", "subject", "watch"]
|
|
32
|
+
.iter()
|
|
33
|
+
.map(|name| (*name).to_owned())
|
|
34
|
+
.collect()
|
|
35
|
+
}),
|
|
36
|
+
procedural_methods: context.setting("ProceduralMethods").unwrap_or_else(|| {
|
|
37
|
+
[
|
|
38
|
+
"benchmark",
|
|
39
|
+
"bm",
|
|
40
|
+
"bmbm",
|
|
41
|
+
"create",
|
|
42
|
+
"each_with_object",
|
|
43
|
+
"measure",
|
|
44
|
+
"new",
|
|
45
|
+
"realtime",
|
|
46
|
+
"tap",
|
|
47
|
+
"with_object",
|
|
48
|
+
]
|
|
49
|
+
.iter()
|
|
50
|
+
.map(|name| (*name).to_owned())
|
|
51
|
+
.collect()
|
|
52
|
+
}),
|
|
53
|
+
procedural_oneliners_may_have_braces: context
|
|
54
|
+
.setting("AllowBracesOnProceduralOneLiners")
|
|
55
|
+
.unwrap_or(false),
|
|
29
56
|
allowed_methods: context.setting("AllowedMethods").unwrap_or_else(|| {
|
|
30
57
|
["lambda", "proc", "it"]
|
|
31
58
|
.iter()
|
|
@@ -84,6 +111,12 @@ struct Cop<'a, 'tree> {
|
|
|
84
111
|
braces_required_methods: Vec<String>,
|
|
85
112
|
allowed_methods: Vec<String>,
|
|
86
113
|
allowed_patterns: Vec<regex::Regex>,
|
|
114
|
+
/// `FunctionalMethods`: the ones whose block always yields a value, so braces suit them.
|
|
115
|
+
functional_methods: Vec<String>,
|
|
116
|
+
/// `ProceduralMethods`: the ones whose block is run for its effect, so `do ... end` suits.
|
|
117
|
+
procedural_methods: Vec<String>,
|
|
118
|
+
/// `AllowBracesOnProceduralOneLiners`.
|
|
119
|
+
procedural_oneliners_may_have_braces: bool,
|
|
87
120
|
}
|
|
88
121
|
|
|
89
122
|
/// One block: its delimiters, the call it hangs off, and the body between them.
|
|
@@ -98,7 +131,7 @@ struct Block<'t> {
|
|
|
98
131
|
}
|
|
99
132
|
|
|
100
133
|
impl<'t> Block<'t> {
|
|
101
|
-
fn new(context: &RuleContext<'_>, node: Node<'t>) -> Option<Self> {
|
|
134
|
+
fn new(context: &'t RuleContext<'_>, node: Node<'t>) -> Option<Self> {
|
|
102
135
|
let braces = node.kind_str() == "block";
|
|
103
136
|
let call = node.parent()?;
|
|
104
137
|
// `-> { }` is a block whose call is `lambda` upstream, however the arrow is written.
|
|
@@ -109,8 +142,8 @@ impl<'t> Block<'t> {
|
|
|
109
142
|
.map(|method| context.source.node_text(method).to_owned())
|
|
110
143
|
.unwrap_or_default(),
|
|
111
144
|
};
|
|
112
|
-
let
|
|
113
|
-
let children: Vec<Node<'t>> = node
|
|
145
|
+
let _cursor = node.walk();
|
|
146
|
+
let children: Vec<Node<'t>> = all_children_iter(node, context).collect();
|
|
114
147
|
let begin = *children
|
|
115
148
|
.iter()
|
|
116
149
|
.find(|child| !child.is_named() && matches!(child.kind_str(), "{" | "do"))?;
|
|
@@ -164,7 +197,7 @@ impl Cop<'_, '_> {
|
|
|
164
197
|
vec![right]
|
|
165
198
|
}
|
|
166
199
|
"element_reference" => {
|
|
167
|
-
let mut arguments = super::nodes::
|
|
200
|
+
let mut arguments = super::nodes::children_in(node, self.context);
|
|
168
201
|
if node.field("object").is_some() && !arguments.is_empty() {
|
|
169
202
|
arguments.remove(0);
|
|
170
203
|
}
|
|
@@ -252,11 +285,117 @@ impl Cop<'_, '_> {
|
|
|
252
285
|
},
|
|
253
286
|
false => block.braces,
|
|
254
287
|
},
|
|
288
|
+
"semantic" => self.semantic_block_style(block),
|
|
255
289
|
// `line_count_based_block_style?`.
|
|
256
290
|
_ => block.multiline != block.braces,
|
|
257
291
|
}
|
|
258
292
|
}
|
|
259
293
|
|
|
294
|
+
/// `semantic_block_style?`: braces mark a block whose value is used, `do ... end` one that is
|
|
295
|
+
/// run for its effect. Which one a block is comes from the method it hangs off and from where
|
|
296
|
+
/// the block itself sits -- a block whose result nothing reads is procedural whatever it does.
|
|
297
|
+
fn semantic_block_style(&self, block: &Block<'_>) -> bool {
|
|
298
|
+
if block.braces {
|
|
299
|
+
self.functional_methods.contains(&block.method)
|
|
300
|
+
|| self.functional_block(block)
|
|
301
|
+
|| (self.procedural_oneliners_may_have_braces && !block.multiline)
|
|
302
|
+
} else {
|
|
303
|
+
self.procedural_methods.contains(&block.method) || !self.return_value_used(block.call)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/// `functional_block?`.
|
|
308
|
+
fn functional_block(&self, block: &Block<'_>) -> bool {
|
|
309
|
+
self.return_value_used(block.call) || self.return_value_of_scope(block.call)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/// `return_value_used?`: the block feeds an assignment or another call.
|
|
313
|
+
///
|
|
314
|
+
/// The node walked from is upstream's `block`, which the grammar spells as the call the braces
|
|
315
|
+
/// hang off, so the parent chain reads the same from there.
|
|
316
|
+
fn return_value_used(&self, node: Node<'_>) -> bool {
|
|
317
|
+
let Some(parent) = node.parent_of(self.context) else {
|
|
318
|
+
return false;
|
|
319
|
+
};
|
|
320
|
+
match parent.kind_str() {
|
|
321
|
+
// `node.parent.begin_type?`: parentheses around the block, which pass the question on.
|
|
322
|
+
"parenthesized_statements" => self.return_value_used(parent),
|
|
323
|
+
"assignment" | "operator_assignment" => true,
|
|
324
|
+
// `call_type?`: Parser folds arguments, indexing and non-keyword operators into
|
|
325
|
+
// `send` nodes. tree-sitter keeps the argument-list wrapper and operator forms.
|
|
326
|
+
"call" | "method_call" | "argument_list" | "element_reference" | "unary" => true,
|
|
327
|
+
// Ordinary operators are `send` nodes to Parser, while `&&` / `||` / `and` / `or`
|
|
328
|
+
// remain keyword operators. The latter make braces functional through
|
|
329
|
+
// `return_value_of_scope?`, but do not make a `do...end` block's value directly used.
|
|
330
|
+
"binary" => parent.field("operator").is_some_and(|operator| {
|
|
331
|
+
!LOGICAL_OPERATORS.contains(&self.context.source.node_text(operator))
|
|
332
|
+
}),
|
|
333
|
+
_ => false,
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/// `return_value_of_scope?`: the block sits where a value is read off -- a condition, an
|
|
338
|
+
/// operand, an element, or the last expression of whatever encloses it.
|
|
339
|
+
fn return_value_of_scope(&self, node: Node<'_>) -> bool {
|
|
340
|
+
let Some(parent) = node.parent_of(self.context) else {
|
|
341
|
+
return false;
|
|
342
|
+
};
|
|
343
|
+
if matches!(
|
|
344
|
+
parent.kind_str(),
|
|
345
|
+
"if" | "unless"
|
|
346
|
+
| "elsif"
|
|
347
|
+
| "if_modifier"
|
|
348
|
+
| "unless_modifier"
|
|
349
|
+
| "while"
|
|
350
|
+
| "until"
|
|
351
|
+
| "while_modifier"
|
|
352
|
+
| "until_modifier"
|
|
353
|
+
| "case"
|
|
354
|
+
| "case_match"
|
|
355
|
+
| "conditional"
|
|
356
|
+
| "array"
|
|
357
|
+
| "range"
|
|
358
|
+
) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
// `operator_keyword?`: `and`, `or`, `not`.
|
|
362
|
+
if parent.kind_str() == "binary"
|
|
363
|
+
&& parent.field("operator").is_some_and(|operator| {
|
|
364
|
+
LOGICAL_OPERATORS.contains(&&self.context.source.text()[operator.byte_range()])
|
|
365
|
+
})
|
|
366
|
+
{
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
if parent.kind_str() == "unary" {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
// `return false unless (parent = node.parent)`: a lone top-level statement **has no
|
|
373
|
+
// parent** upstream. The grammar always hands back a `program`, whose only child is that
|
|
374
|
+
// statement -- reading it as "the last thing the parent holds" made every such block look
|
|
375
|
+
// like the value of its scope, and `each { |x| x }` went unreported under `semantic`.
|
|
376
|
+
if parent.kind_str() == "program" && super::nodes::children(parent).len() == 1 {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
// `parent.children.last == node`: the block is the last thing the parent holds, so its
|
|
380
|
+
// value is the parent's.
|
|
381
|
+
let children = super::nodes::children(parent);
|
|
382
|
+
// Parser represents a method-level rescue as a `rescue` whose protected body is a
|
|
383
|
+
// separate `begin`. tree-sitter appends the rescue clauses to one `body_statement`; the
|
|
384
|
+
// last expression before the first clause is the value of that virtual begin.
|
|
385
|
+
let value_children = match parent.kind_str() {
|
|
386
|
+
"body_statement" => {
|
|
387
|
+
&children[..children
|
|
388
|
+
.iter()
|
|
389
|
+
.position(|child| matches!(child.kind_str(), "rescue" | "else" | "ensure"))
|
|
390
|
+
.unwrap_or(children.len())]
|
|
391
|
+
}
|
|
392
|
+
_ => children.as_slice(),
|
|
393
|
+
};
|
|
394
|
+
value_children
|
|
395
|
+
.last()
|
|
396
|
+
.is_some_and(|last| last.id() == node.id())
|
|
397
|
+
}
|
|
398
|
+
|
|
260
399
|
fn braces_required_method(&self, method: &str) -> bool {
|
|
261
400
|
self.braces_required_methods
|
|
262
401
|
.iter()
|
|
@@ -271,7 +410,7 @@ impl Cop<'_, '_> {
|
|
|
271
410
|
let Some(body) = block.body() else {
|
|
272
411
|
return false;
|
|
273
412
|
};
|
|
274
|
-
let clauses = super::nodes::
|
|
413
|
+
let clauses = super::nodes::children_in(body, self.context);
|
|
275
414
|
if clauses.iter().any(|child| child.kind_str() == "ensure") {
|
|
276
415
|
return true;
|
|
277
416
|
}
|
|
@@ -351,7 +490,7 @@ impl Cop<'_, '_> {
|
|
|
351
490
|
// `begin_required?`: a protected body needs its own `begin` once the keywords are gone.
|
|
352
491
|
if block.multiline
|
|
353
492
|
&& let Some(body) = block.body()
|
|
354
|
-
&& super::nodes::
|
|
493
|
+
&& super::nodes::children_in(body, self.context)
|
|
355
494
|
.iter()
|
|
356
495
|
.any(|child| matches!(child.kind_str(), "rescue" | "ensure"))
|
|
357
496
|
{
|
|
@@ -445,10 +584,15 @@ fn get_blocks(node: Node<'_>, out: &mut Vec<Range<usize>>) {
|
|
|
445
584
|
/// Whether the call carrying the block is itself the receiver of another one.
|
|
446
585
|
fn chained(call: Node<'_>) -> bool {
|
|
447
586
|
call.parent().is_some_and(|parent| {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
587
|
+
let receiver = match parent.kind_str() {
|
|
588
|
+
"call" | "method_call" => parent.field("receiver"),
|
|
589
|
+
// `foo { ... }[key]` is a `send` of `:[]` upstream and therefore extends the same
|
|
590
|
+
// chain as a dotted call. The grammar gives indexing its own node and calls the
|
|
591
|
+
// receiver `object`.
|
|
592
|
+
"element_reference" => parent.field("object"),
|
|
593
|
+
_ => None,
|
|
594
|
+
};
|
|
595
|
+
receiver.is_some_and(|receiver| receiver.id() == call.id())
|
|
452
596
|
})
|
|
453
597
|
}
|
|
454
598
|
|
|
@@ -3,6 +3,7 @@ use tree_sitter::Node;
|
|
|
3
3
|
use crate::diagnostic::{Edit, Offense};
|
|
4
4
|
use crate::rules::RuleContext;
|
|
5
5
|
use crate::rules::node_ext::NodeExt;
|
|
6
|
+
use crate::rules::send_node::named_children_iter;
|
|
6
7
|
|
|
7
8
|
const MSG: &str = "Avoid the use of the case equality operator `===`.";
|
|
8
9
|
|
|
@@ -71,6 +72,10 @@ fn case_equality<'tree>(
|
|
|
71
72
|
))
|
|
72
73
|
}
|
|
73
74
|
_ => {
|
|
75
|
+
// `on_send` is not aliased to `on_csend`: `a&.===(b)` is outside this cop.
|
|
76
|
+
if !crate::rules::send_node::is_plain_send(node, context) {
|
|
77
|
+
return None;
|
|
78
|
+
}
|
|
74
79
|
let method = node.field("method")?;
|
|
75
80
|
if context.source.node_text(method) != "===" {
|
|
76
81
|
return None;
|
|
@@ -99,8 +104,8 @@ fn replacement(
|
|
|
99
104
|
let _ = node;
|
|
100
105
|
let source = context.source.node_text(left);
|
|
101
106
|
if left.kind_str() == "parenthesized_statements" {
|
|
102
|
-
let
|
|
103
|
-
let inner = left
|
|
107
|
+
let _cursor = left.walk();
|
|
108
|
+
let inner = named_children_iter(left, context).next()?;
|
|
104
109
|
if inner.kind_str() != "range" {
|
|
105
110
|
return None;
|
|
106
111
|
}
|
|
@@ -39,7 +39,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
39
39
|
let branches = branch_conditions(node);
|
|
40
40
|
// `elsif_conditional?` and `min_branches_count?`: the chain has to be an `if`/`elsif` one
|
|
41
41
|
// with enough arms to be worth a `case`.
|
|
42
|
-
if branches.len() < 2 ||
|
|
42
|
+
if branches.len() < 2 || branch_count(node) < minimum {
|
|
43
43
|
continue;
|
|
44
44
|
}
|
|
45
45
|
let Some(target) = find_target(context, branches[0]) else {
|
|
@@ -88,6 +88,30 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/// `if_conditional_branches`: the arms a conditional chain has, which is what `MinBranchesCount`
|
|
92
|
+
/// is counted against. **A ternary is an `if` upstream**, so an `else` holding one contributes its
|
|
93
|
+
/// own arm -- `if a / elsif b / else c ? d : e` has three, not two.
|
|
94
|
+
fn branch_count(node: Node<'_>) -> usize {
|
|
95
|
+
let mut count = 0;
|
|
96
|
+
let mut current = Some(node);
|
|
97
|
+
while let Some(branch) = current {
|
|
98
|
+
if !matches!(branch.kind_str(), "if" | "elsif" | "conditional") {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
count += 1;
|
|
102
|
+
current = match branch.field("alternative") {
|
|
103
|
+
Some(alternative) if alternative.kind_str() == "else" => {
|
|
104
|
+
match super::nodes::children(alternative).as_slice() {
|
|
105
|
+
[only] => Some(*only),
|
|
106
|
+
_ => None,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
other => other,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
count
|
|
113
|
+
}
|
|
114
|
+
|
|
91
115
|
/// `branch_conditions`: the condition of every `if`/`elsif` in the chain.
|
|
92
116
|
fn branch_conditions<'t>(node: Node<'t>) -> Vec<Node<'t>> {
|
|
93
117
|
let mut conditions = Vec::new();
|
|
@@ -159,7 +183,7 @@ fn collect_conditions(
|
|
|
159
183
|
conditions: &mut Vec<String>,
|
|
160
184
|
) -> bool {
|
|
161
185
|
if node.kind_str() == "parenthesized_statements" {
|
|
162
|
-
let Some(inner) = super::nodes::
|
|
186
|
+
let Some(inner) = super::nodes::children_in(node, context).into_iter().next() else {
|
|
163
187
|
return false;
|
|
164
188
|
};
|
|
165
189
|
return collect_conditions(context, inner, target, conditions);
|
|
@@ -3,6 +3,8 @@ use tree_sitter::Node;
|
|
|
3
3
|
use crate::diagnostic::{Edit, Offense};
|
|
4
4
|
use crate::rules::RuleContext;
|
|
5
5
|
use crate::rules::node_ext::NodeExt;
|
|
6
|
+
use crate::rules::send_node::all_children_of;
|
|
7
|
+
use crate::rules::send_node::named_children_iter;
|
|
6
8
|
|
|
7
9
|
const NESTED_MSG: &str = "Use nested module/class definitions instead of compact style.";
|
|
8
10
|
const COMPACT_MSG: &str = "Use compact module/class definition instead of nested style.";
|
|
@@ -61,12 +63,20 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
61
63
|
|
|
62
64
|
if style == "nested" {
|
|
63
65
|
check_nested(context, node, name, offenses);
|
|
64
|
-
} else if
|
|
66
|
+
} else if let [only] = body_statements(node).as_slice()
|
|
67
|
+
&& matches!(only.kind_str(), "class" | "module")
|
|
65
68
|
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
let inner_style = match only.kind_str() == "class" {
|
|
70
|
+
true => for_classes.as_str(),
|
|
71
|
+
false => for_modules.as_str(),
|
|
72
|
+
};
|
|
73
|
+
let offense = context.offense(COMPACT_MSG, name.byte_range());
|
|
74
|
+
offenses.push(
|
|
75
|
+
match compact_correction(context, node, name, *only, inner_style) {
|
|
76
|
+
Some(edits) => offense.corrected_by_all(edits),
|
|
77
|
+
None => offense,
|
|
78
|
+
},
|
|
79
|
+
);
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
}
|
|
@@ -158,9 +168,9 @@ fn nested_correction(
|
|
|
158
168
|
if closing.kind_str() != "end" {
|
|
159
169
|
return None;
|
|
160
170
|
}
|
|
161
|
-
let
|
|
162
|
-
let separator = name
|
|
163
|
-
.
|
|
171
|
+
let _cursor = name.walk();
|
|
172
|
+
let separator = all_children_of(name, context)
|
|
173
|
+
.into_iter()
|
|
164
174
|
.find(|child| child.kind_str() == "::")?;
|
|
165
175
|
|
|
166
176
|
let (_, column) = context.source.line_column(node.start_byte());
|
|
@@ -200,6 +210,122 @@ fn nested_correction(
|
|
|
200
210
|
])
|
|
201
211
|
}
|
|
202
212
|
|
|
213
|
+
/// `compact_definition`: `class Foo` holding nothing but `class Bar` becomes `class Foo::Bar`.
|
|
214
|
+
///
|
|
215
|
+
/// Three edits, as upstream: the header up to the inner name, the inner `end`, and the body's
|
|
216
|
+
/// indentation. The last one is a run of per-line edits rather than a single replacement, so the
|
|
217
|
+
/// cops correcting inside the body still can in the same pass.
|
|
218
|
+
fn compact_correction(
|
|
219
|
+
context: &RuleContext<'_>,
|
|
220
|
+
node: Node<'_>,
|
|
221
|
+
name: Node<'_>,
|
|
222
|
+
inner: Node<'_>,
|
|
223
|
+
inner_style: &str,
|
|
224
|
+
) -> Option<Vec<Edit>> {
|
|
225
|
+
// Compacting produces a definition whose own style resolves to `nested`, which would make the
|
|
226
|
+
// correction ping-pong between the two forms.
|
|
227
|
+
if inner_style == "nested" {
|
|
228
|
+
return None;
|
|
229
|
+
}
|
|
230
|
+
let keyword = node.child(0)?;
|
|
231
|
+
let inner_name = inner.field("name")?;
|
|
232
|
+
let inner_end = inner.child(inner.child_count().saturating_sub(1) as u32)?;
|
|
233
|
+
if inner_end.kind_str() != "end" {
|
|
234
|
+
return None;
|
|
235
|
+
}
|
|
236
|
+
// The cop is declared `SafeAutoCorrect: false`: compacting removes a definition of the
|
|
237
|
+
// namespace, so the result needs it defined elsewhere.
|
|
238
|
+
let safe = false;
|
|
239
|
+
let compacted = format!(
|
|
240
|
+
"{} {}::{}",
|
|
241
|
+
inner.kind_str(),
|
|
242
|
+
context.source.node_text(name),
|
|
243
|
+
context.source.node_text(inner_name)
|
|
244
|
+
);
|
|
245
|
+
// `compact_replacement`: the comments `ast_with_comments` ties to the inner definition stand
|
|
246
|
+
// inside the range being replaced, so they have to be written back above the compacted line.
|
|
247
|
+
let comments = leading_comments(context, inner);
|
|
248
|
+
let replacement = match comments.is_empty() {
|
|
249
|
+
true => compacted,
|
|
250
|
+
false => format!("{}\n{compacted}", comments.join("\n")),
|
|
251
|
+
};
|
|
252
|
+
let mut edits = vec![Edit {
|
|
253
|
+
start: keyword.start_byte(),
|
|
254
|
+
end: inner_name.end_byte(),
|
|
255
|
+
replacement,
|
|
256
|
+
safe,
|
|
257
|
+
}];
|
|
258
|
+
|
|
259
|
+
// `remove_end`: the inner `end`, the blanks in front of it and the line break after it.
|
|
260
|
+
let same_line = inner_name.end_position().row == inner_end.start_position().row;
|
|
261
|
+
let leading = leading_spaces(context, inner.start_byte());
|
|
262
|
+
let removal_start = match same_line {
|
|
263
|
+
true => inner_name.end_byte(),
|
|
264
|
+
false => inner_end.start_byte().saturating_sub(leading.len()),
|
|
265
|
+
};
|
|
266
|
+
let adjustment = match context.source.text().as_bytes().get(removal_start) {
|
|
267
|
+
Some(b';') => 0,
|
|
268
|
+
_ => 1,
|
|
269
|
+
};
|
|
270
|
+
edits.push(Edit {
|
|
271
|
+
start: removal_start,
|
|
272
|
+
end: (inner_end.end_byte() + adjustment).min(context.source.text().len()),
|
|
273
|
+
replacement: String::new(),
|
|
274
|
+
safe,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
edits.extend(unindent(context, node, inner, inner_name.end_byte(), safe));
|
|
278
|
+
Some(edits)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// `unindent`: the body moves left by the difference between the configured width and the
|
|
282
|
+
/// indentation the inner definition gave it.
|
|
283
|
+
fn unindent(
|
|
284
|
+
context: &RuleContext<'_>,
|
|
285
|
+
node: Node<'_>,
|
|
286
|
+
inner: Node<'_>,
|
|
287
|
+
header_end: usize,
|
|
288
|
+
safe: bool,
|
|
289
|
+
) -> Vec<Edit> {
|
|
290
|
+
let Some(last) = body_statements(inner).last().copied() else {
|
|
291
|
+
return Vec::new();
|
|
292
|
+
};
|
|
293
|
+
let outer_indent = leading_spaces(context, node.start_byte()).chars().count();
|
|
294
|
+
let inner_indent = leading_spaces(context, last.start_byte()).chars().count();
|
|
295
|
+
if outer_indent == inner_indent {
|
|
296
|
+
return Vec::new();
|
|
297
|
+
}
|
|
298
|
+
let width: usize = context
|
|
299
|
+
.setting("IndentationWidth")
|
|
300
|
+
.or_else(|| context.setting_of("Layout/IndentationWidth", "Width"))
|
|
301
|
+
.unwrap_or(2);
|
|
302
|
+
let Some(delta) = inner_indent.checked_sub(width).filter(|delta| *delta > 0) else {
|
|
303
|
+
return Vec::new();
|
|
304
|
+
};
|
|
305
|
+
// Only the lines the header edit does not already cover.
|
|
306
|
+
let first = context.source.line_column(header_end).0 + 1;
|
|
307
|
+
let last_line = context.source.line_column(node.end_byte()).0;
|
|
308
|
+
(first..last_line)
|
|
309
|
+
.filter_map(|line| {
|
|
310
|
+
let text = context.source.line(line);
|
|
311
|
+
let removable = text
|
|
312
|
+
.chars()
|
|
313
|
+
// Upstream removes a run of literal spaces. A tab is one source character but
|
|
314
|
+
// does not match that run, so tab-indented bodies remain byte-for-byte intact.
|
|
315
|
+
.take_while(|character| *character == ' ')
|
|
316
|
+
.count()
|
|
317
|
+
.min(delta);
|
|
318
|
+
let start = context.source.line_start(line);
|
|
319
|
+
(removable > 0).then(|| Edit {
|
|
320
|
+
start,
|
|
321
|
+
end: start + removable,
|
|
322
|
+
replacement: String::new(),
|
|
323
|
+
safe,
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
.collect()
|
|
327
|
+
}
|
|
328
|
+
|
|
203
329
|
/// `padding.sub(' ' * column, '')`: one run of that many spaces comes back out of the padding.
|
|
204
330
|
fn drop_one_run(padding: &str, spaces: usize) -> String {
|
|
205
331
|
let run = " ".repeat(spaces);
|
|
@@ -232,8 +358,8 @@ fn namespace_keyword(context: &RuleContext<'_>, node: Node<'_>, name: Node<'_>)
|
|
|
232
358
|
{
|
|
233
359
|
return "class";
|
|
234
360
|
}
|
|
235
|
-
let
|
|
236
|
-
stack.extend(current
|
|
361
|
+
let _cursor = current.walk();
|
|
362
|
+
stack.extend(named_children_iter(current, context));
|
|
237
363
|
}
|
|
238
364
|
"module"
|
|
239
365
|
}
|
|
@@ -264,6 +390,32 @@ fn normalized(text: &str) -> String {
|
|
|
264
390
|
.collect()
|
|
265
391
|
}
|
|
266
392
|
|
|
393
|
+
/// `ast_with_comments[node]` for a definition: the run of comment lines written directly above it,
|
|
394
|
+
/// in source order and without their indentation, which is what `Comment#text` gives.
|
|
395
|
+
fn leading_comments(context: &RuleContext<'_>, node: Node<'_>) -> Vec<String> {
|
|
396
|
+
let opening = context.source.line_column(node.start_byte()).0;
|
|
397
|
+
let mut collected = Vec::new();
|
|
398
|
+
let mut probe = opening;
|
|
399
|
+
while probe > 1 {
|
|
400
|
+
let line = context.source.line(probe - 1);
|
|
401
|
+
let text = line.trim();
|
|
402
|
+
// A `#` opening a line is a comment only if the tokeniser agrees -- a heredoc body can
|
|
403
|
+
// hold anything.
|
|
404
|
+
let is_comment = text.starts_with('#')
|
|
405
|
+
&& context
|
|
406
|
+
.comment_ranges()
|
|
407
|
+
.iter()
|
|
408
|
+
.any(|comment| context.source.line_column(comment.start).0 == probe - 1);
|
|
409
|
+
if !is_comment {
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
collected.push(text.to_owned());
|
|
413
|
+
probe -= 1;
|
|
414
|
+
}
|
|
415
|
+
collected.reverse();
|
|
416
|
+
collected
|
|
417
|
+
}
|
|
418
|
+
|
|
267
419
|
/// `leading_spaces`: the indentation of the line the definition opens on.
|
|
268
420
|
fn leading_spaces(context: &RuleContext<'_>, offset: usize) -> String {
|
|
269
421
|
let (line, _) = context.source.line_column(offset);
|
|
@@ -17,7 +17,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
17
17
|
};
|
|
18
18
|
// Only the statements the body holds directly are looked at: a definition nested inside a
|
|
19
19
|
// `begin` or a conditional is not a child of the class body upstream either.
|
|
20
|
-
for definition in super::nodes::
|
|
20
|
+
for definition in super::nodes::children_in(body, context) {
|
|
21
21
|
if definition.kind_str() != "singleton_method" {
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
@@ -60,6 +60,15 @@ fn check_singleton_class(node: Node<'_>, context: &RuleContext<'_>, offenses: &m
|
|
|
60
60
|
if ranges.iter().any(|range| range.end >= node.end_byte()) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
+
// A definition written on the `class << self` line itself leaves the correction asking the
|
|
64
|
+
// rewriter for a range it cannot build (`RangeError`), and the offense goes with it.
|
|
65
|
+
let keyword_line = context.source.line_column(node.start_byte()).0;
|
|
66
|
+
if definitions
|
|
67
|
+
.iter()
|
|
68
|
+
.any(|definition| context.source.line_column(definition.start_byte()).0 == keyword_line)
|
|
69
|
+
{
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
63
72
|
let mut rewritten: Vec<String> = definitions
|
|
64
73
|
.iter()
|
|
65
74
|
.zip(&ranges)
|