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
|
@@ -6,9 +6,14 @@ use crate::diagnostic::Offense;
|
|
|
6
6
|
use crate::rules::RuleContext;
|
|
7
7
|
use crate::rules::node_ext::NodeExt;
|
|
8
8
|
|
|
9
|
+
use super::blocks::BlockArgs;
|
|
10
|
+
use super::locals::LocalVariables;
|
|
11
|
+
use crate::rules::send_node::named_children_iter;
|
|
12
|
+
|
|
9
13
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
10
14
|
let mut tracker = Tracker {
|
|
11
15
|
context,
|
|
16
|
+
locals: LocalVariables::new(context),
|
|
12
17
|
// Only the line of the earlier definition is ever read back, so the node itself does not
|
|
13
18
|
// have to be kept alive alongside the key.
|
|
14
19
|
definitions: HashMap::new(),
|
|
@@ -27,6 +32,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
27
32
|
|
|
28
33
|
struct Tracker<'a, 'tree> {
|
|
29
34
|
context: &'a RuleContext<'tree>,
|
|
35
|
+
locals: LocalVariables<'a, 'tree>,
|
|
30
36
|
definitions: HashMap<String, usize>,
|
|
31
37
|
/// Keys already redefined once inside a `rescue` or `ensure`. A body with such a clause is
|
|
32
38
|
/// allowed one redefinition -- that is how a conditional fallback definition is written -- but
|
|
@@ -53,9 +59,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
53
59
|
if has_if_ancestor(node) {
|
|
54
60
|
return;
|
|
55
61
|
}
|
|
56
|
-
let (Some(name), Some(receiver)) =
|
|
57
|
-
(self.name_text(node), node.field("object"))
|
|
58
|
-
else {
|
|
62
|
+
let (Some(name), Some(receiver)) = (self.name_text(node), node.field("object")) else {
|
|
59
63
|
return;
|
|
60
64
|
};
|
|
61
65
|
match receiver.kind_str() {
|
|
@@ -71,10 +75,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
fn on_alias(&mut self, node: Node<'_>, offenses: &mut Vec<Offense>) {
|
|
74
|
-
let (Some(name), Some(original)) = (
|
|
75
|
-
node.field("name"),
|
|
76
|
-
node.field("alias"),
|
|
77
|
-
) else {
|
|
78
|
+
let (Some(name), Some(original)) = (node.field("name"), node.field("alias")) else {
|
|
78
79
|
return;
|
|
79
80
|
};
|
|
80
81
|
let (Some(name), Some(original)) = (
|
|
@@ -118,6 +119,20 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
accessor @ ("attr" | "attr_reader" | "attr_writer" | "attr_accessor") => {
|
|
122
|
+
// `attr :foo, true` carries a `true` that is no symbol, and `literal_arguments`
|
|
123
|
+
// answers a call with any such argument with nothing at all. `on_attr` reads only
|
|
124
|
+
// the first argument of an `attr`, so the name is recovered from that alone.
|
|
125
|
+
let arguments = match (accessor, arguments.is_empty()) {
|
|
126
|
+
("attr", true) => attr_first_name(self.context, node)
|
|
127
|
+
.map(|name| vec![name])
|
|
128
|
+
.unwrap_or_default(),
|
|
129
|
+
_ => arguments,
|
|
130
|
+
};
|
|
131
|
+
// **`found_attr` names each argument through `sym_name`, which answers `nil` for a
|
|
132
|
+
// `str`.** `attr_reader 'foo'` defines the method all the same, but upstream never
|
|
133
|
+
// records it -- so pairing it with `attr_reader :foo` reported a duplicate that
|
|
134
|
+
// upstream is silent about.
|
|
135
|
+
let arguments: Vec<String> = attr_symbol_names(node, &arguments);
|
|
121
136
|
if arguments.is_empty() || !in_macro_scope(node) {
|
|
122
137
|
return;
|
|
123
138
|
}
|
|
@@ -151,8 +166,10 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
151
166
|
offenses: &mut Vec<Offense>,
|
|
152
167
|
) {
|
|
153
168
|
let (readable, writable) = match accessor {
|
|
154
|
-
// `attr :foo, true` is the
|
|
155
|
-
|
|
169
|
+
// `writable = args.size == 2 && args.last.true_type?`: `attr :foo, true` is the
|
|
170
|
+
// historical accessor form, and it defines the writer as well. `literal_arguments`
|
|
171
|
+
// keeps only the symbols, so the `true` has to be read off the call itself.
|
|
172
|
+
"attr" => (true, attr_with_writer(node)),
|
|
156
173
|
"attr_reader" => (true, false),
|
|
157
174
|
"attr_writer" => (false, true),
|
|
158
175
|
_ => (true, true),
|
|
@@ -173,12 +190,13 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
173
190
|
}
|
|
174
191
|
|
|
175
192
|
fn found_instance_method(&mut self, node: Node<'_>, name: &str, offenses: &mut Vec<Offense>) {
|
|
176
|
-
if let Some(scope) = parent_module_name(self.context, node) {
|
|
193
|
+
if let Some(scope) = parent_module_name(self.context, node, &self.locals) {
|
|
177
194
|
let method = format!("{}{name}", humanize_scope(&scope));
|
|
178
195
|
self.found_method(node, &method, None, offenses);
|
|
179
196
|
} else if let Some(anon_block) = anonymous_class_block(self.context, node) {
|
|
180
|
-
let base =
|
|
181
|
-
|
|
197
|
+
let base = qualified_object_scope(
|
|
198
|
+
parent_module_name(self.context, anon_block, &self.locals).as_deref(),
|
|
199
|
+
);
|
|
182
200
|
let scope = if singleton_class_ancestor(node).is_some() {
|
|
183
201
|
format!("#<Class:{base}>")
|
|
184
202
|
} else {
|
|
@@ -193,11 +211,12 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
fn check_self_receiver(&mut self, node: Node<'_>, name: &str, offenses: &mut Vec<Offense>) {
|
|
196
|
-
if let Some(enclosing) = parent_module_name(self.context, node) {
|
|
214
|
+
if let Some(enclosing) = parent_module_name(self.context, node, &self.locals) {
|
|
197
215
|
self.found_method(node, &format!("{enclosing}.{name}"), None, offenses);
|
|
198
216
|
} else if let Some(anon_block) = anonymous_class_block(self.context, node) {
|
|
199
|
-
let scope =
|
|
200
|
-
|
|
217
|
+
let scope = qualified_object_scope(
|
|
218
|
+
parent_module_name(self.context, anon_block, &self.locals).as_deref(),
|
|
219
|
+
);
|
|
201
220
|
let scope_id = anon_block_scope_id(self.context, anon_block);
|
|
202
221
|
self.found_method(node, &format!("{scope}.{name}"), scope_id, offenses);
|
|
203
222
|
}
|
|
@@ -212,13 +231,17 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
212
231
|
let Some(receiver) = sclass.field("value") else {
|
|
213
232
|
return;
|
|
214
233
|
};
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
let
|
|
219
|
-
|
|
234
|
+
// `singleton_receiver_node.send_type?` -- and a bare `blah` **is** a send upstream. The
|
|
235
|
+
// grammar spells a receiverless call as an `identifier`, so `class << blah` never reached
|
|
236
|
+
// the check and every method defined in it went unrecorded.
|
|
237
|
+
let method = match receiver.kind_str() {
|
|
238
|
+
"call" => match receiver.field("method") {
|
|
239
|
+
Some(method) => self.text(method).to_owned(),
|
|
240
|
+
None => return,
|
|
241
|
+
},
|
|
242
|
+
"identifier" => self.text(receiver).to_owned(),
|
|
243
|
+
_ => return,
|
|
220
244
|
};
|
|
221
|
-
let method = self.text(method).to_owned();
|
|
222
245
|
self.found_method(node, &format!("{method}.{name}"), None, offenses);
|
|
223
246
|
}
|
|
224
247
|
|
|
@@ -261,7 +284,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
261
284
|
}
|
|
262
285
|
|
|
263
286
|
fn track_self_alias(&mut self, node: Node<'_>, name: &str) {
|
|
264
|
-
if let Some(scope) = parent_module_name(self.context, node) {
|
|
287
|
+
if let Some(scope) = parent_module_name(self.context, node, &self.locals) {
|
|
265
288
|
self.self_aliased
|
|
266
289
|
.insert(format!("{}{name}", humanize_scope(&scope)));
|
|
267
290
|
}
|
|
@@ -283,7 +306,7 @@ impl<'a, 'tree> Tracker<'a, 'tree> {
|
|
|
283
306
|
if let Some(defined) = defined_module_name(self.context, parent) {
|
|
284
307
|
let bare = defined.rsplit("::").next().unwrap_or(&defined);
|
|
285
308
|
if bare == const_name.trim_start_matches("::") {
|
|
286
|
-
let enclosing = parent_module_name(self.context, parent);
|
|
309
|
+
let enclosing = parent_module_name(self.context, parent, &self.locals);
|
|
287
310
|
return Some(match enclosing.as_deref() {
|
|
288
311
|
Some("Object") | None => defined,
|
|
289
312
|
Some(name) => format!("{name}::{defined}"),
|
|
@@ -371,7 +394,11 @@ fn rescue_scope(node: Node<'_>) -> Option<&'static str> {
|
|
|
371
394
|
|
|
372
395
|
/// The lexical namespace enclosing `node`, or `None` when a block breaks the chain -- a block body
|
|
373
396
|
/// is ordinary code, so what a `def` inside one attaches to cannot be read off the source.
|
|
374
|
-
fn parent_module_name(
|
|
397
|
+
fn parent_module_name(
|
|
398
|
+
context: &RuleContext<'_>,
|
|
399
|
+
node: Node<'_>,
|
|
400
|
+
locals: &LocalVariables<'_, '_>,
|
|
401
|
+
) -> Option<String> {
|
|
375
402
|
let mut parts: Vec<String> = Vec::new();
|
|
376
403
|
let mut current = node;
|
|
377
404
|
while let Some(parent) = current.parent_of(context) {
|
|
@@ -381,12 +408,22 @@ fn parent_module_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<Strin
|
|
|
381
408
|
parts.push(name);
|
|
382
409
|
}
|
|
383
410
|
}
|
|
384
|
-
"singleton_class" => parts.push(singleton_class_name(context, parent)?),
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
411
|
+
"singleton_class" => parts.push(singleton_class_name(context, parent, locals)?),
|
|
412
|
+
// `each_ancestor(:class, :module, :sclass, :casgn, :block)` does not list `numblock`
|
|
413
|
+
// or `itblock`: a block whose parameter is implicit is a different node type upstream,
|
|
414
|
+
// and the walk passes straight over it. The grammar spells all three as `block`.
|
|
415
|
+
"block" | "do_block"
|
|
416
|
+
if matches!(
|
|
417
|
+
BlockArgs::of(parent, context, locals),
|
|
418
|
+
BlockArgs::Written(_)
|
|
419
|
+
) =>
|
|
420
|
+
{
|
|
421
|
+
match block_module_name(context, parent) {
|
|
422
|
+
BlockScope::Transparent => {}
|
|
423
|
+
BlockScope::Named(name) => parts.push(name),
|
|
424
|
+
BlockScope::Opaque => return None,
|
|
425
|
+
}
|
|
426
|
+
}
|
|
390
427
|
_ => {}
|
|
391
428
|
}
|
|
392
429
|
current = parent;
|
|
@@ -417,11 +454,13 @@ fn defined_module_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<Stri
|
|
|
417
454
|
}
|
|
418
455
|
|
|
419
456
|
fn constant_name(context: &RuleContext<'_>, node: Node<'_>) -> String {
|
|
420
|
-
context
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
457
|
+
let text = context.source.node_text(node);
|
|
458
|
+
// `qualified_name` interpolates `namespace.const_name`, and `(self)` has none -- `self::A`
|
|
459
|
+
// qualifies as `::A`.
|
|
460
|
+
if let Some(rest) = text.strip_prefix("self::") {
|
|
461
|
+
return format!("::{rest}");
|
|
462
|
+
}
|
|
463
|
+
text.trim_start_matches("::").to_owned()
|
|
425
464
|
}
|
|
426
465
|
|
|
427
466
|
/// `Class.new`/`Module.new`, optionally carrying a block. `global` demands the bare global
|
|
@@ -438,10 +477,7 @@ fn is_class_or_module_new(context: &RuleContext<'_>, node: Node<'_>, global: boo
|
|
|
438
477
|
if !crate::rules::send_node::is_plain_send(call, context) {
|
|
439
478
|
return false;
|
|
440
479
|
}
|
|
441
|
-
let (Some(receiver), Some(method)) = (
|
|
442
|
-
call.field("receiver"),
|
|
443
|
-
call.field("method"),
|
|
444
|
-
) else {
|
|
480
|
+
let (Some(receiver), Some(method)) = (call.field("receiver"), call.field("method")) else {
|
|
445
481
|
return false;
|
|
446
482
|
};
|
|
447
483
|
if context.source.node_text(method) != "new" {
|
|
@@ -452,10 +488,15 @@ fn is_class_or_module_new(context: &RuleContext<'_>, node: Node<'_>, global: boo
|
|
|
452
488
|
return false;
|
|
453
489
|
}
|
|
454
490
|
let bare = name.rsplit("::").next().unwrap_or(&name);
|
|
455
|
-
matches!(receiver.kind_str(), "constant" | "scope_resolution")
|
|
491
|
+
matches!(receiver.kind_str(), "constant" | "scope_resolution")
|
|
492
|
+
&& matches!(bare, "Class" | "Module")
|
|
456
493
|
}
|
|
457
494
|
|
|
458
|
-
fn singleton_class_name(
|
|
495
|
+
fn singleton_class_name(
|
|
496
|
+
context: &RuleContext<'_>,
|
|
497
|
+
node: Node<'_>,
|
|
498
|
+
locals: &LocalVariables<'_, '_>,
|
|
499
|
+
) -> Option<String> {
|
|
459
500
|
let value = node.field("value")?;
|
|
460
501
|
match value.kind_str() {
|
|
461
502
|
"constant" | "scope_resolution" => {
|
|
@@ -463,7 +504,7 @@ fn singleton_class_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<Str
|
|
|
463
504
|
}
|
|
464
505
|
"self" => Some(format!(
|
|
465
506
|
"#<Class:{}>",
|
|
466
|
-
parent_module_name(context, node).unwrap_or_default()
|
|
507
|
+
parent_module_name(context, node, locals).unwrap_or_default()
|
|
467
508
|
)),
|
|
468
509
|
_ => None,
|
|
469
510
|
}
|
|
@@ -479,7 +520,10 @@ enum BlockScope {
|
|
|
479
520
|
}
|
|
480
521
|
|
|
481
522
|
fn block_module_name(context: &RuleContext<'_>, block: Node<'_>) -> BlockScope {
|
|
482
|
-
let Some(call) = block
|
|
523
|
+
let Some(call) = block
|
|
524
|
+
.parent_of(context)
|
|
525
|
+
.filter(|parent| parent.kind_str() == "call")
|
|
526
|
+
else {
|
|
483
527
|
return BlockScope::Opaque;
|
|
484
528
|
};
|
|
485
529
|
// `on_send` は `csend` に呼ばれないので、`Class&.new do` のブロックは本家からは名前の付かない
|
|
@@ -733,15 +777,12 @@ fn named_receiver<'a>(context: &'a RuleContext<'_>, call: Node<'_>) -> Option<(&
|
|
|
733
777
|
}
|
|
734
778
|
|
|
735
779
|
fn is_class_new_block(context: &RuleContext<'_>, block: Node<'_>) -> bool {
|
|
736
|
-
block
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
.is_some_and(|name| name == "Class")
|
|
743
|
-
})
|
|
744
|
-
&& is_class_or_module_new(context, block, false)
|
|
780
|
+
block.field("receiver").is_some_and(|receiver| {
|
|
781
|
+
constant_name(context, receiver)
|
|
782
|
+
.rsplit("::")
|
|
783
|
+
.next()
|
|
784
|
+
.is_some_and(|name| name == "Class")
|
|
785
|
+
}) && is_class_or_module_new(context, block, false)
|
|
745
786
|
}
|
|
746
787
|
|
|
747
788
|
fn anon_block_identity(context: &RuleContext<'_>, block: Node<'_>) -> String {
|
|
@@ -781,9 +822,9 @@ fn literal_arguments(context: &RuleContext<'_>, node: Node<'_>) -> Vec<String> {
|
|
|
781
822
|
let Some(arguments) = node.field("arguments") else {
|
|
782
823
|
return Vec::new();
|
|
783
824
|
};
|
|
784
|
-
let
|
|
825
|
+
let _cursor = arguments.walk();
|
|
785
826
|
let mut names = Vec::new();
|
|
786
|
-
for argument in arguments
|
|
827
|
+
for argument in named_children_iter(arguments, context) {
|
|
787
828
|
match symbol_name(context.source.node_text(argument)) {
|
|
788
829
|
Some(name) if matches!(argument.kind_str(), "simple_symbol" | "string") => {
|
|
789
830
|
names.push(name.to_owned());
|
|
@@ -807,3 +848,41 @@ fn symbol_name(text: &str) -> Option<&str> {
|
|
|
807
848
|
.unwrap_or(text);
|
|
808
849
|
(!unquoted.is_empty() && !unquoted.contains(['#', '"', '\''])).then_some(unquoted)
|
|
809
850
|
}
|
|
851
|
+
|
|
852
|
+
/// `args.size == 2 && args.last.true_type?`: the second argument of the historical `attr` form.
|
|
853
|
+
fn attr_with_writer(node: Node<'_>) -> bool {
|
|
854
|
+
let Some(list) = node.field("arguments") else {
|
|
855
|
+
return false;
|
|
856
|
+
};
|
|
857
|
+
let written = crate::rules::send_node::named_children(list);
|
|
858
|
+
matches!(written.as_slice(), [_, last] if last.kind_str() == "true")
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/// The name the first argument of an `attr` spells, when a later argument stopped
|
|
862
|
+
/// `literal_arguments` from answering.
|
|
863
|
+
fn attr_first_name(context: &RuleContext<'_>, node: Node<'_>) -> Option<String> {
|
|
864
|
+
let list = node.field("arguments")?;
|
|
865
|
+
let first = *crate::rules::send_node::named_children_of(list, context).first()?;
|
|
866
|
+
if !matches!(first.kind_str(), "simple_symbol" | "string") {
|
|
867
|
+
return None;
|
|
868
|
+
}
|
|
869
|
+
symbol_name(context.source.node_text(first)).map(str::to_owned)
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/// The `attr` arguments upstream records: `sym_name` answers only for a `sym`, so an accessor
|
|
873
|
+
/// named with a string contributes nothing.
|
|
874
|
+
fn attr_symbol_names(node: Node<'_>, fallback: &[String]) -> Vec<String> {
|
|
875
|
+
let Some(list) = node.field("arguments") else {
|
|
876
|
+
return fallback.to_vec();
|
|
877
|
+
};
|
|
878
|
+
let written = crate::rules::send_node::named_children(list);
|
|
879
|
+
if written.len() != fallback.len() {
|
|
880
|
+
return fallback.to_vec();
|
|
881
|
+
}
|
|
882
|
+
written
|
|
883
|
+
.iter()
|
|
884
|
+
.zip(fallback)
|
|
885
|
+
.filter(|(argument, _)| argument.kind_str() == "simple_symbol")
|
|
886
|
+
.map(|(_, name)| name.clone())
|
|
887
|
+
.collect()
|
|
888
|
+
}
|
|
@@ -156,7 +156,6 @@ fn body_parent(container: Node<'_>) -> Parent {
|
|
|
156
156
|
/// statements it holds.
|
|
157
157
|
fn split_body(container: Node<'_>) -> bool {
|
|
158
158
|
let mut cursor = container.walk();
|
|
159
|
-
container
|
|
160
|
-
.named_children(&mut cursor)
|
|
159
|
+
container.named_children(&mut cursor)
|
|
161
160
|
.any(|child| matches!(child.kind_str(), "rescue" | "else" | "ensure"))
|
|
162
161
|
}
|
|
@@ -6,6 +6,7 @@ use crate::rules::node_ext::NodeExt;
|
|
|
6
6
|
use crate::rules::send_node::{arguments, named_children};
|
|
7
7
|
|
|
8
8
|
use super::node_equality::identical;
|
|
9
|
+
use crate::rules::send_node::named_children_of;
|
|
9
10
|
|
|
10
11
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
11
12
|
for node in context.nodes_of_any(&["call", "element_reference"]) {
|
|
@@ -15,7 +16,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
15
16
|
let mut seen: Vec<Node<'_>> = Vec::new();
|
|
16
17
|
for (index, &element) in elements.iter().enumerate() {
|
|
17
18
|
// Only a value the cop can compare: anything computed may differ at run time.
|
|
18
|
-
if !is_comparable(element) {
|
|
19
|
+
if !is_comparable(element, context) {
|
|
19
20
|
continue;
|
|
20
21
|
}
|
|
21
22
|
if !seen.iter().any(|other| identical(*other, element, context)) {
|
|
@@ -41,12 +42,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
41
42
|
/// `set_init_elements`, paired with the name the message calls the class by.
|
|
42
43
|
fn set_init_elements<'tree>(
|
|
43
44
|
node: Node<'tree>,
|
|
44
|
-
context: &RuleContext<'_>,
|
|
45
|
+
context: &'tree RuleContext<'_>,
|
|
45
46
|
) -> Option<(String, Vec<Node<'tree>>)> {
|
|
46
47
|
if node.kind_str() == "element_reference" {
|
|
47
48
|
let object = node.field("object")?;
|
|
48
49
|
let name = set_class_name(object, context)?;
|
|
49
|
-
let elements =
|
|
50
|
+
let elements = named_children_of(node, context)
|
|
50
51
|
.into_iter()
|
|
51
52
|
.filter(|child| child.kind_str() != "comment" && child.id() != object.id())
|
|
52
53
|
.collect();
|
|
@@ -62,7 +63,10 @@ fn set_init_elements<'tree>(
|
|
|
62
63
|
return None;
|
|
63
64
|
};
|
|
64
65
|
let array = only.first();
|
|
65
|
-
|
|
66
|
+
// `%i[…]` and `%w[…]` are `array` nodes upstream; the grammar names them by their
|
|
67
|
+
// percent spelling, so `Set.new(%i[foo bar foo])` fell out of the arm entirely.
|
|
68
|
+
matches!(array.kind_str(), "array" | "symbol_array" | "string_array")
|
|
69
|
+
.then(|| (name, literal_elements(array)))
|
|
66
70
|
}
|
|
67
71
|
// `(call (array $...) :to_set)`: the class is not written, so the message names `Set`.
|
|
68
72
|
"to_set" => {
|
|
@@ -95,7 +99,12 @@ fn set_class_name(node: Node<'_>, context: &RuleContext<'_>) -> Option<String> {
|
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
/// `literal? || const_type? || variable?`: what the cop is willing to call the same twice.
|
|
98
|
-
fn is_comparable(node: Node<'_>) -> bool {
|
|
102
|
+
fn is_comparable(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
103
|
+
// `variable?` is `lvar`/`ivar`/`cvar`/`gvar`. A bare name that was never assigned is a `send`
|
|
104
|
+
// upstream -- `Set[foo, foo]` calls `foo` twice and may well get two different values.
|
|
105
|
+
if node.kind_str() == "identifier" && !context.variable_analysis().is_variable_reference(node) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
99
108
|
matches!(
|
|
100
109
|
node.kind_str(),
|
|
101
110
|
"integer"
|
|
@@ -105,6 +114,9 @@ fn is_comparable(node: Node<'_>) -> bool {
|
|
|
105
114
|
| "string"
|
|
106
115
|
| "simple_symbol"
|
|
107
116
|
| "delimited_symbol"
|
|
117
|
+
// The elements of a `%i[…]` / `%w[…]`, which upstream spells as plain `sym` and `str`.
|
|
118
|
+
| "bare_symbol"
|
|
119
|
+
| "bare_string"
|
|
108
120
|
| "hash_key_symbol"
|
|
109
121
|
| "character"
|
|
110
122
|
| "true"
|
|
@@ -122,6 +134,8 @@ fn is_comparable(node: Node<'_>) -> bool {
|
|
|
122
134
|
| "instance_variable"
|
|
123
135
|
| "class_variable"
|
|
124
136
|
| "global_variable"
|
|
137
|
+
// `variable?` covers a local variable, which the grammar spells as a bare identifier.
|
|
138
|
+
| "identifier"
|
|
125
139
|
| "self"
|
|
126
140
|
)
|
|
127
141
|
}
|
|
@@ -5,6 +5,7 @@ use crate::rules::RuleContext;
|
|
|
5
5
|
|
|
6
6
|
use super::statements::statements;
|
|
7
7
|
use crate::rules::node_ext::NodeExt;
|
|
8
|
+
use crate::rules::send_node::all_children_iter;
|
|
8
9
|
|
|
9
10
|
/// The three conditionals whose branch can be written empty. A modifier form always has a body.
|
|
10
11
|
const CONDITIONALS: &[&str] = &["if", "unless", "elsif"];
|
|
@@ -35,6 +36,21 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
35
36
|
let alternative = node.field("alternative");
|
|
36
37
|
let range = match alternative {
|
|
37
38
|
Some(clause) => node.start_byte()..clause.start_byte(),
|
|
39
|
+
// `node.source_range` ends at the last node the **parser** built, and a comment is not
|
|
40
|
+
// one. An `elsif` holding nothing but a comment therefore ends at its condition; an
|
|
41
|
+
// `if` still reaches its own `end`, which the grammar keeps inside the node.
|
|
42
|
+
None if node.kind_str() == "elsif" => {
|
|
43
|
+
let end = node
|
|
44
|
+
.field("condition")
|
|
45
|
+
.map_or_else(|| node.end_byte(), |condition| condition.end_byte());
|
|
46
|
+
// The parser's range keeps the separator that closes the clause -- `elsif cond;`
|
|
47
|
+
// ends after the semicolon -- and stops before a comment or a line break.
|
|
48
|
+
let end = match context.source.text()[end..node.end_byte()].chars().next() {
|
|
49
|
+
Some(';') => end + 1,
|
|
50
|
+
_ => end,
|
|
51
|
+
};
|
|
52
|
+
node.start_byte()..end
|
|
53
|
+
}
|
|
38
54
|
None => node.byte_range(),
|
|
39
55
|
};
|
|
40
56
|
let mut offense =
|
|
@@ -166,7 +182,7 @@ fn enclosing_end<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<
|
|
|
166
182
|
/// `node.loc.begin`: the `then` or `;` the branch was introduced with. The grammar puts a `then`
|
|
167
183
|
/// inside the branch it opens, but a `;` stays beside the condition -- and a branch holding nothing
|
|
168
184
|
/// is no node at all, which leaves the `;` as the only trace of it.
|
|
169
|
-
fn then_keyword<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<Node<'tree>> {
|
|
185
|
+
fn then_keyword<'tree>(node: Node<'tree>, context: &'tree RuleContext<'_>) -> Option<Node<'tree>> {
|
|
170
186
|
if let Some(first) = node
|
|
171
187
|
.field("consequence")
|
|
172
188
|
.and_then(|branch| branch.child(0))
|
|
@@ -175,8 +191,8 @@ fn then_keyword<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<N
|
|
|
175
191
|
return Some(first);
|
|
176
192
|
}
|
|
177
193
|
let condition = node.field("condition")?;
|
|
178
|
-
let
|
|
179
|
-
node
|
|
194
|
+
let _cursor = node.walk();
|
|
195
|
+
all_children_iter(node, context).find(|child| {
|
|
180
196
|
child.start_byte() >= condition.end_byte()
|
|
181
197
|
&& matches!(context.source.node_text(*child), "then" | ";")
|
|
182
198
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use crate::diagnostic::Offense;
|
|
2
2
|
use crate::rules::RuleContext;
|
|
3
|
+
use crate::rules::node_ext::NodeExt;
|
|
3
4
|
|
|
4
5
|
use super::statements::statements;
|
|
5
6
|
|
|
@@ -8,7 +9,21 @@ const MSG: &str = "Avoid empty expressions.";
|
|
|
8
9
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
9
10
|
// `on_begin` reaches the `begin` node that `(...)` builds and the one an interpolation holds,
|
|
10
11
|
// but never the `kwbegin` of `begin ... end`, which is a type of its own upstream.
|
|
11
|
-
for node in context.nodes_of_any(&[
|
|
12
|
+
for node in context.nodes_of_any(&[
|
|
13
|
+
"parenthesized_statements",
|
|
14
|
+
"interpolation",
|
|
15
|
+
// **`return()` writes its parentheses as an argument list here.** Upstream builds the
|
|
16
|
+
// same empty `begin` a bare `()` does, so the jump's empty parentheses are an empty
|
|
17
|
+
// expression too -- the grammar just files them under a different kind.
|
|
18
|
+
"argument_list",
|
|
19
|
+
]) {
|
|
20
|
+
if node.kind_str() == "argument_list"
|
|
21
|
+
&& node
|
|
22
|
+
.parent()
|
|
23
|
+
.is_none_or(|parent| !matches!(parent.kind_str(), "return" | "break" | "next"))
|
|
24
|
+
{
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
12
27
|
if statements(node).is_empty() {
|
|
13
28
|
offenses.push(context.offense(MSG, node.byte_range()));
|
|
14
29
|
}
|
|
@@ -11,6 +11,7 @@ use super::comments_help::comment_search_lines;
|
|
|
11
11
|
use crate::ruby_version::RubyVersion;
|
|
12
12
|
|
|
13
13
|
use super::statements::statements;
|
|
14
|
+
use crate::rules::send_node::named_children_iter;
|
|
14
15
|
|
|
15
16
|
const MSG: &str = "Avoid `in` branches without a body.";
|
|
16
17
|
|
|
@@ -20,8 +21,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
20
21
|
}
|
|
21
22
|
let allow_comments: bool = context.setting("AllowComments").unwrap_or(true);
|
|
22
23
|
for case in context.nodes_of("case_match") {
|
|
23
|
-
let
|
|
24
|
-
let children: Vec<Node<'_>> = case
|
|
24
|
+
let _cursor = case.walk();
|
|
25
|
+
let children: Vec<Node<'_>> = named_children_iter(case, context).collect();
|
|
25
26
|
for (index, &branch) in children.iter().enumerate() {
|
|
26
27
|
if branch.kind_str() != "in_clause" || has_body(branch) {
|
|
27
28
|
continue;
|
|
@@ -8,14 +8,15 @@ use crate::rules::RuleContext;
|
|
|
8
8
|
use crate::rules::node_ext::NodeExt;
|
|
9
9
|
|
|
10
10
|
use super::comments_help::comment_search_lines;
|
|
11
|
+
use crate::rules::send_node::named_children_iter;
|
|
11
12
|
|
|
12
13
|
const MSG: &str = "Avoid `when` branches without a body.";
|
|
13
14
|
|
|
14
15
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
15
16
|
let allow_comments: bool = context.setting("AllowComments").unwrap_or(true);
|
|
16
17
|
for case in context.nodes_of("case") {
|
|
17
|
-
let
|
|
18
|
-
let children: Vec<Node<'_>> = case
|
|
18
|
+
let _cursor = case.walk();
|
|
19
|
+
let children: Vec<Node<'_>> = named_children_iter(case, context).collect();
|
|
19
20
|
for (index, &branch) in children.iter().enumerate() {
|
|
20
21
|
if branch.kind_str() != "when" || has_body(branch) {
|
|
21
22
|
continue;
|
|
@@ -32,8 +33,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
32
33
|
/// the `;` or `then` that separates it from the body it does not have is no part of it.
|
|
33
34
|
fn reported_range(branch: Node<'_>) -> std::ops::Range<usize> {
|
|
34
35
|
let mut cursor = branch.walk();
|
|
35
|
-
let last = branch
|
|
36
|
-
.named_children(&mut cursor)
|
|
36
|
+
let last = branch.named_children(&mut cursor)
|
|
37
37
|
.filter(|child| child.kind_str() == "pattern")
|
|
38
38
|
.last();
|
|
39
39
|
branch.start_byte()..last.map_or_else(|| branch.end_byte(), |last| last.end_byte())
|
|
@@ -5,6 +5,7 @@ use crate::rules::RuleContext;
|
|
|
5
5
|
|
|
6
6
|
use super::statements::statements;
|
|
7
7
|
use crate::rules::node_ext::NodeExt;
|
|
8
|
+
use crate::rules::send_node::named_children_iter;
|
|
8
9
|
|
|
9
10
|
const MSG: &str = "Do not return from an `ensure` block.";
|
|
10
11
|
|
|
@@ -26,8 +27,8 @@ fn collect(
|
|
|
26
27
|
if node.kind_str() == "return" && !returns_from_inner_scope(node, ensure_node, context) {
|
|
27
28
|
offenses.push(context.offense(MSG, node.byte_range()));
|
|
28
29
|
}
|
|
29
|
-
let
|
|
30
|
-
let children: Vec<Node<'_>> = node
|
|
30
|
+
let _cursor = node.walk();
|
|
31
|
+
let children: Vec<Node<'_>> = named_children_iter(node, context).collect();
|
|
31
32
|
for child in children {
|
|
32
33
|
collect(child, ensure_node, context, offenses);
|
|
33
34
|
}
|
|
@@ -2,8 +2,10 @@ use tree_sitter::Node;
|
|
|
2
2
|
|
|
3
3
|
use crate::diagnostic::Offense;
|
|
4
4
|
use crate::rules::RuleContext;
|
|
5
|
-
use crate::rules::send_node::{Argument, arguments,
|
|
5
|
+
use crate::rules::send_node::{Argument, arguments, send_range};
|
|
6
6
|
use crate::rules::node_ext::NodeExt;
|
|
7
|
+
use crate::rules::send_node::named_children_of;
|
|
8
|
+
use crate::rules::send_node::named_children_iter;
|
|
7
9
|
|
|
8
10
|
const MSG_EQUALITY: &str = "Avoid equality comparisons of floats as they are unreliable.";
|
|
9
11
|
const MSG_INEQUALITY: &str = "Avoid inequality comparisons of floats as they are unreliable.";
|
|
@@ -52,15 +54,15 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
52
54
|
|
|
53
55
|
/// `on_case`: every condition of every `when` branch, taken on its own.
|
|
54
56
|
fn case_conditions(node: Node<'_>, context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
55
|
-
for branch in
|
|
57
|
+
for branch in named_children_of(node, context) {
|
|
56
58
|
if branch.kind_str() != "when" {
|
|
57
59
|
continue;
|
|
58
60
|
}
|
|
59
|
-
for pattern in
|
|
61
|
+
for pattern in named_children_of(branch, context) {
|
|
60
62
|
if pattern.kind_str() != "pattern" {
|
|
61
63
|
continue;
|
|
62
64
|
}
|
|
63
|
-
let Some(condition) =
|
|
65
|
+
let Some(condition) = named_children_iter(pattern, context).next() else {
|
|
64
66
|
continue;
|
|
65
67
|
};
|
|
66
68
|
if is_float(condition, context) && !literal_safe(condition, context) {
|
|
@@ -110,7 +112,7 @@ fn is_float(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
|
110
112
|
// The parser folds a sign into the numeric literal it stands before.
|
|
111
113
|
"unary" => signed_numeric(node, context).is_some_and(|operand| operand.kind_str() == "float"),
|
|
112
114
|
"binary" | "call" => float_send(node, context),
|
|
113
|
-
"parenthesized_statements" =>
|
|
115
|
+
"parenthesized_statements" => named_children_of(node, context)
|
|
114
116
|
.into_iter()
|
|
115
117
|
.next()
|
|
116
118
|
.is_some_and(|first| is_float(first, context)),
|
|
@@ -167,7 +169,7 @@ fn literal_safe(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
|
167
169
|
"nil" => true,
|
|
168
170
|
"integer" | "float" | "rational" | "complex" => is_zero(node, context),
|
|
169
171
|
"unary" => signed_numeric(node, context).is_some_and(|operand| is_zero(operand, context)),
|
|
170
|
-
"parenthesized_statements" =>
|
|
172
|
+
"parenthesized_statements" => named_children_of(node, context)
|
|
171
173
|
.into_iter()
|
|
172
174
|
.next()
|
|
173
175
|
.is_some_and(|first| literal_safe(first, context)),
|