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
|
@@ -13,6 +13,11 @@ use crate::rules::node_ext::NodeExt;
|
|
|
13
13
|
const FORMAT_METHODS: &[&str] = &["format", "sprintf", "printf"];
|
|
14
14
|
|
|
15
15
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
16
|
+
// Every sequence this cop can report starts with `%`. Avoid collecting and splitting every
|
|
17
|
+
// string literal in files that cannot possibly contain one.
|
|
18
|
+
if !context.source.text().contains('%') {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
16
21
|
let style = parse_style(
|
|
17
22
|
context
|
|
18
23
|
.setting::<String>("EnforcedStyle")
|
|
@@ -26,17 +31,29 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
26
31
|
.setting::<String>("Mode")
|
|
27
32
|
.is_some_and(|mode| mode == "conservative");
|
|
28
33
|
let allowed_methods: Vec<String> = context.setting("AllowedMethods").unwrap_or_default();
|
|
34
|
+
// `allowed_method?(name) || matches_allowed_pattern?(name)`: both are matched against the
|
|
35
|
+
// **enclosing call's method name**, not against the string.
|
|
36
|
+
let allowed_patterns =
|
|
37
|
+
crate::rules::naming::support::forbidden_patterns_named(context, "AllowedPatterns");
|
|
29
38
|
|
|
30
39
|
for literal in literals(context) {
|
|
31
|
-
if surrounded_by_command_or_regexp(literal.anchor)
|
|
40
|
+
if surrounded_by_command_or_regexp(literal.anchor, context)
|
|
32
41
|
|| allowed_method(context, literal.anchor, &allowed_methods)
|
|
42
|
+
|| allowed_call_pattern(context, literal.anchor, &allowed_patterns)
|
|
33
43
|
{
|
|
34
44
|
continue;
|
|
35
45
|
}
|
|
36
46
|
// `format_string_in_typical_context?` reads the node `on_str` was handed: for a literal the
|
|
37
47
|
// parser split into parts that is the enclosing `dstr`, which is never a call's argument.
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
// `format_string_context?` is `format_string_in_typical_context?(node) || any ancestor
|
|
49
|
+
// `dstr` in one`. The anchor **is** the node upstream asks about, whether the parser split
|
|
50
|
+
// it into parts or not -- requiring a single part made every interpolated format string
|
|
51
|
+
// uncorrectable, and `format("c#{b}%{template}")` was reported and then left alone.
|
|
52
|
+
let typical = typical_context(context, literal.anchor);
|
|
53
|
+
// Whether the parser cut the literal into parts, which is what puts a `dstr` between each
|
|
54
|
+
// part and the call.
|
|
55
|
+
let split = literal.parts.len() > 1
|
|
56
|
+
|| crate::rules::send_node::has_interpolation(literal.anchor);
|
|
40
57
|
let correctable = typical || enclosing_typical_context(context, literal.anchor);
|
|
41
58
|
|
|
42
59
|
for part in &literal.parts {
|
|
@@ -50,8 +67,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
50
67
|
.filter(|sequence| {
|
|
51
68
|
// `allowed_string?`: an unannotated token outside a format call is left alone,
|
|
52
69
|
// and `conservative` mode extends that to every token.
|
|
70
|
+
//
|
|
71
|
+
// **The node `on_str` is handed is the part, not the literal.** A `"#{x} %s"`
|
|
72
|
+
// splits into a `dstr` of parts, and a part's parent is that `dstr` rather
|
|
73
|
+
// than the call -- so `format_string_in_typical_context?` is false there and
|
|
74
|
+
// every unannotated token in an interpolated format string is allowed.
|
|
53
75
|
let allowed = sequence.style == SequenceStyle::Unannotated || conservative;
|
|
54
|
-
!(allowed && !typical)
|
|
76
|
+
!(allowed && !(typical && !split))
|
|
55
77
|
})
|
|
56
78
|
.collect();
|
|
57
79
|
if detected.is_empty() || allowed_unannotated(&detected, style, max_unannotated) {
|
|
@@ -84,7 +106,7 @@ fn literals<'a>(context: &'a RuleContext<'_>) -> Vec<Literal<'a>> {
|
|
|
84
106
|
// `"%s" %[a]` is a `send` upstream: the second literal is the operator's argument, not a
|
|
85
107
|
// string at all.
|
|
86
108
|
if open.id() == close.id()
|
|
87
|
-
|| (super::percent::is_modulo_operand(node)
|
|
109
|
+
|| (super::percent::is_modulo_operand(node, context)
|
|
88
110
|
&& context.source.node_text(node).starts_with('%'))
|
|
89
111
|
{
|
|
90
112
|
continue;
|
|
@@ -203,14 +225,17 @@ fn typical_context(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
|
203
225
|
if parent.kind_str() != "argument_list" {
|
|
204
226
|
return false;
|
|
205
227
|
}
|
|
206
|
-
let Some(call) = parent
|
|
228
|
+
let Some(call) = parent
|
|
229
|
+
.parent_of(context)
|
|
230
|
+
.filter(|call| call.kind_str() == "call")
|
|
231
|
+
else {
|
|
207
232
|
return false;
|
|
208
233
|
};
|
|
209
234
|
let named = call
|
|
210
235
|
.field("method")
|
|
211
236
|
.is_some_and(|method| FORMAT_METHODS.contains(&context.source.node_text(method)));
|
|
212
237
|
named
|
|
213
|
-
&& super::nodes::
|
|
238
|
+
&& super::nodes::children_in(parent, context)
|
|
214
239
|
.first()
|
|
215
240
|
.is_some_and(|first| first.id() == node.id())
|
|
216
241
|
}
|
|
@@ -220,8 +245,10 @@ fn typical_context(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
|
220
245
|
fn enclosing_typical_context(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
221
246
|
let mut current = node.parent_of(context);
|
|
222
247
|
while let Some(parent) = current {
|
|
223
|
-
if matches!(
|
|
224
|
-
|
|
248
|
+
if matches!(
|
|
249
|
+
parent.kind_str(),
|
|
250
|
+
"chained_string" | "string" | "heredoc_body"
|
|
251
|
+
) && typical_context(context, parent)
|
|
225
252
|
{
|
|
226
253
|
return true;
|
|
227
254
|
}
|
|
@@ -232,13 +259,13 @@ fn enclosing_typical_context(context: &RuleContext<'_>, node: Node<'_>) -> bool
|
|
|
232
259
|
|
|
233
260
|
/// `format_string_token?`: a literal written inside a command or a regexp is none of this cop's
|
|
234
261
|
/// business.
|
|
235
|
-
fn surrounded_by_command_or_regexp(node: Node<'_>) -> bool {
|
|
236
|
-
let mut current =
|
|
262
|
+
fn surrounded_by_command_or_regexp(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
263
|
+
let mut current = context.parent(node);
|
|
237
264
|
while let Some(parent) = current {
|
|
238
265
|
if matches!(parent.kind_str(), "subshell" | "regex") {
|
|
239
266
|
return true;
|
|
240
267
|
}
|
|
241
|
-
current =
|
|
268
|
+
current = context.parent(parent);
|
|
242
269
|
}
|
|
243
270
|
false
|
|
244
271
|
}
|
|
@@ -355,3 +382,25 @@ fn describe(style: SequenceStyle) -> &'static str {
|
|
|
355
382
|
_ => "unannotated tokens (like `%s`)",
|
|
356
383
|
}
|
|
357
384
|
}
|
|
385
|
+
|
|
386
|
+
/// `matches_allowed_pattern?(send_parent.method_name)`.
|
|
387
|
+
fn allowed_call_pattern(
|
|
388
|
+
context: &RuleContext<'_>,
|
|
389
|
+
anchor: Node<'_>,
|
|
390
|
+
patterns: &[®ex::Regex],
|
|
391
|
+
) -> bool {
|
|
392
|
+
if patterns.is_empty() {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
let mut current = anchor.parent_of(context);
|
|
396
|
+
while let Some(node) = current {
|
|
397
|
+
if node.kind_str() == "call"
|
|
398
|
+
&& let Some(method) = node.field("method")
|
|
399
|
+
{
|
|
400
|
+
let name = context.source.node_text(method);
|
|
401
|
+
return patterns.iter().any(|pattern| pattern.is_match(name));
|
|
402
|
+
}
|
|
403
|
+
current = node.parent_of(context);
|
|
404
|
+
}
|
|
405
|
+
false
|
|
406
|
+
}
|
|
@@ -2,6 +2,7 @@ use std::ops::Range;
|
|
|
2
2
|
|
|
3
3
|
use crate::diagnostic::{Edit, Offense};
|
|
4
4
|
use crate::magic_comment::MagicComment;
|
|
5
|
+
use crate::ruby_version::RubyVersion;
|
|
5
6
|
use crate::rules::RuleContext;
|
|
6
7
|
|
|
7
8
|
const MSG_MISSING: &str = "Missing frozen string literal comment.";
|
|
@@ -9,8 +10,16 @@ const MSG_MISSING_TRUE: &str = "Missing magic comment `# frozen_string_literal:
|
|
|
9
10
|
const MSG_UNNECESSARY: &str = "Unnecessary frozen string literal comment.";
|
|
10
11
|
const MSG_DISABLED: &str = "Frozen string literal comment must be set to `true`.";
|
|
11
12
|
|
|
13
|
+
/// `minimum_target_ruby_version 2.3`: the frozen string literal comment is only valid in Ruby 2.3+.
|
|
14
|
+
const MINIMUM: RubyVersion = RubyVersion::new(2, 3);
|
|
15
|
+
|
|
12
16
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
13
|
-
if context.
|
|
17
|
+
if context.target_ruby_version() < MINIMUM {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// `processed_source.tokens.empty?`: a file of nothing but a data section has no tokens at
|
|
21
|
+
// all, and the cop returns before it looks for a comment.
|
|
22
|
+
if crate::rules::support::code_before_data(context).trim().is_empty() {
|
|
14
23
|
return;
|
|
15
24
|
}
|
|
16
25
|
let style: String = context
|
|
@@ -19,6 +28,15 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
19
28
|
|
|
20
29
|
match style.as_str() {
|
|
21
30
|
"never" => {
|
|
31
|
+
// `ensure_no_comment` asks `frozen_string_literal_comment_exists?`, which reads only
|
|
32
|
+
// the **leading** comments -- a magic comment written after code does not reach Ruby,
|
|
33
|
+
// so there is nothing unnecessary to remove. The offense itself is then placed on the
|
|
34
|
+
// comment found anywhere, which is what `specified_comment` returns.
|
|
35
|
+
let leading = leading_comments(context)
|
|
36
|
+
.any(|(_, line)| MagicComment::parse(line).valid_literal_value());
|
|
37
|
+
if !leading {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
22
40
|
if let Some(comment) = specified_comment(context) {
|
|
23
41
|
offenses.push(
|
|
24
42
|
context
|
|
@@ -44,7 +62,10 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
44
62
|
.corrected_by(Edit {
|
|
45
63
|
start: comment.start,
|
|
46
64
|
end: comment.end,
|
|
47
|
-
|
|
65
|
+
// `MagicComment#new_frozen_string_literal(true)` keeps the comment's
|
|
66
|
+
// own shape -- `# -*- frozen_string_literal: false -*-` becomes the
|
|
67
|
+
// same line with `true`, not a plain one.
|
|
68
|
+
replacement: enabled_form(context.source.slice(comment.clone())),
|
|
48
69
|
safe: false,
|
|
49
70
|
}),
|
|
50
71
|
);
|
|
@@ -151,9 +172,21 @@ fn line_content_end(context: &RuleContext<'_>, line_number: usize) -> usize {
|
|
|
151
172
|
/// Removes the comment along with the line it sits on, so no blank line is left behind.
|
|
152
173
|
fn remove_comment(context: &RuleContext<'_>, comment: Range<usize>) -> Edit {
|
|
153
174
|
let line_number = context.source.line_column(comment.start).0;
|
|
175
|
+
let start = context.source.line_start(line_number);
|
|
176
|
+
// `range_with_surrounding_space(node.pos, side: :right)`: the blank lines the comment left
|
|
177
|
+
// behind go with it, so removing it does not open a gap at the head of the file.
|
|
178
|
+
let text = context.source.text();
|
|
179
|
+
let mut end = context.source.line_range(line_number).end;
|
|
180
|
+
while text[end..]
|
|
181
|
+
.chars()
|
|
182
|
+
.next()
|
|
183
|
+
.is_some_and(|character| character.is_whitespace())
|
|
184
|
+
{
|
|
185
|
+
end += text[end..].chars().next().map_or(0, char::len_utf8);
|
|
186
|
+
}
|
|
154
187
|
Edit {
|
|
155
|
-
start
|
|
156
|
-
end
|
|
188
|
+
start,
|
|
189
|
+
end,
|
|
157
190
|
replacement: String::new(),
|
|
158
191
|
safe: false,
|
|
159
192
|
}
|
|
@@ -187,3 +220,28 @@ fn specified_comment(context: &RuleContext<'_>) -> Option<Range<usize>> {
|
|
|
187
220
|
Some(start..start + trimmed.len())
|
|
188
221
|
})
|
|
189
222
|
}
|
|
223
|
+
|
|
224
|
+
/// `MagicComment#new_frozen_string_literal(true)`: the value changes and nothing else does.
|
|
225
|
+
fn enabled_form(comment: &str) -> String {
|
|
226
|
+
let mut out = String::with_capacity(comment.len());
|
|
227
|
+
let mut rest = comment;
|
|
228
|
+
while let Some(index) = rest.find("frozen_string_literal") {
|
|
229
|
+
let (before, after) = rest.split_at(index + "frozen_string_literal".len());
|
|
230
|
+
out.push_str(before);
|
|
231
|
+
let value_start = after
|
|
232
|
+
.find(|character: char| character != ':' && character != ' ' && character != '=')
|
|
233
|
+
.unwrap_or(after.len());
|
|
234
|
+
out.push_str(&after[..value_start]);
|
|
235
|
+
let tail = &after[value_start..];
|
|
236
|
+
let value_end = tail
|
|
237
|
+
.find(|character: char| !character.is_ascii_alphanumeric())
|
|
238
|
+
.unwrap_or(tail.len());
|
|
239
|
+
out.push_str("true");
|
|
240
|
+
rest = &tail[value_end..];
|
|
241
|
+
}
|
|
242
|
+
out.push_str(rest);
|
|
243
|
+
match out.is_empty() {
|
|
244
|
+
true => "# frozen_string_literal: true".to_owned(),
|
|
245
|
+
false => out,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -11,6 +11,7 @@ use super::conditional::{
|
|
|
11
11
|
use crate::diagnostic::{Edit, Offense};
|
|
12
12
|
use crate::rules::RuleContext;
|
|
13
13
|
use crate::rules::node_ext::NodeExt;
|
|
14
|
+
use crate::rules::send_node::named_children_of;
|
|
14
15
|
|
|
15
16
|
const MSG: &str = "Use a guard clause (`%<example>s`) instead of wrapping the code inside a \
|
|
16
17
|
conditional expression.";
|
|
@@ -331,7 +332,7 @@ impl Cop<'_, '_> {
|
|
|
331
332
|
/// The span of one branch, which is the whole statement list when it holds more than one.
|
|
332
333
|
fn branch_range(&self, node: Node<'_>, field: &str) -> Option<Range<usize>> {
|
|
333
334
|
let clause = node.field(field)?;
|
|
334
|
-
let statements = super::nodes::
|
|
335
|
+
let statements = super::nodes::children_in(clause, self.context);
|
|
335
336
|
let first = statements.first()?;
|
|
336
337
|
let last = statements.last()?;
|
|
337
338
|
Some(first.start_byte()..last.end_byte())
|
|
@@ -421,7 +422,7 @@ impl Cop<'_, '_> {
|
|
|
421
422
|
/// *descendant* of the condition holds, while `if x, y = foo` writes two.
|
|
422
423
|
fn assigned_locals(&self, condition: Node<'_>) -> Vec<String> {
|
|
423
424
|
let mut names = Vec::new();
|
|
424
|
-
for inner in descendants(condition) {
|
|
425
|
+
for inner in descendants(condition, self.context) {
|
|
425
426
|
if !matches!(inner.kind_str(), "assignment" | "operator_assignment") {
|
|
426
427
|
continue;
|
|
427
428
|
}
|
|
@@ -454,13 +455,13 @@ impl Cop<'_, '_> {
|
|
|
454
455
|
let body = body_of(consequence);
|
|
455
456
|
let roots: Vec<Node<'_>> = match &body {
|
|
456
457
|
Body::Missing => return Vec::new(),
|
|
457
|
-
Body::One(node) => super::nodes::
|
|
458
|
+
Body::One(node) => super::nodes::children_in(*node, self.context),
|
|
458
459
|
Body::Begin(statements) => statements.clone(),
|
|
459
460
|
};
|
|
460
461
|
let mut names = Vec::new();
|
|
461
462
|
let mut pending: Vec<Node<'_>> = roots;
|
|
462
463
|
while let Some(root) = pending.pop() {
|
|
463
|
-
for inner in descendants(root) {
|
|
464
|
+
for inner in descendants(root, self.context) {
|
|
464
465
|
if inner.kind_str() == "heredoc_beginning"
|
|
465
466
|
&& let Some(body) = self.heredoc_body(inner)
|
|
466
467
|
{
|
|
@@ -602,9 +603,9 @@ fn heredoc_end(context: &RuleContext<'_>, beginning: Node<'_>) -> Option<Range<u
|
|
|
602
603
|
.nodes_of("heredoc_beginning")
|
|
603
604
|
.position(|node| node.id() == beginning.id())?;
|
|
604
605
|
let body = context.nodes_of("heredoc_body").nth(index)?;
|
|
605
|
-
let
|
|
606
|
-
let terminator = body
|
|
607
|
-
.
|
|
606
|
+
let _cursor = body.walk();
|
|
607
|
+
let terminator = named_children_of(body, context)
|
|
608
|
+
.into_iter()
|
|
608
609
|
.find(|child| child.kind_str() == "heredoc_end")?;
|
|
609
610
|
Some(terminator.byte_range())
|
|
610
611
|
}
|
|
@@ -142,8 +142,8 @@ fn trailing_comma(context: &RuleContext<'_>, end: usize) -> Option<usize> {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/// `array.values`: the elements, with the trailing key-value pairs folded into one hash.
|
|
145
|
-
fn values<'tree>(context: &RuleContext<'_>, array: Node<'tree>) -> Vec<Value<'tree>> {
|
|
146
|
-
let children = super::nodes::
|
|
145
|
+
fn values<'tree>(context: &'tree RuleContext<'_>, array: Node<'tree>) -> Vec<Value<'tree>> {
|
|
146
|
+
let children = super::nodes::children_in(array, context);
|
|
147
147
|
let split = children.iter().position(|child| is_pair(*child));
|
|
148
148
|
let (elements, pairs) = match split {
|
|
149
149
|
Some(index) => children.split_at(index),
|
|
@@ -158,10 +158,10 @@ fn values<'tree>(context: &RuleContext<'_>, array: Node<'tree>) -> Vec<Value<'tr
|
|
|
158
158
|
is_hash: element.kind_str() == "hash",
|
|
159
159
|
braces: element.kind_str() == "hash",
|
|
160
160
|
opens_with_splat: element.kind_str() == "hash"
|
|
161
|
-
&& super::nodes::
|
|
161
|
+
&& super::nodes::children_in(*element, context)
|
|
162
162
|
.first()
|
|
163
163
|
.is_some_and(|first| first.kind_str() == "hash_splat_argument"),
|
|
164
|
-
empty: element.kind_str() == "hash" && super::nodes::
|
|
164
|
+
empty: element.kind_str() == "hash" && super::nodes::children_in(*element, context).is_empty(),
|
|
165
165
|
})
|
|
166
166
|
.collect();
|
|
167
167
|
let _ = context;
|
|
@@ -200,7 +200,7 @@ fn on_block_pass(context: &RuleContext<'_>, offenses: &mut Vec<Offense>, node: N
|
|
|
200
200
|
if block_pass.kind_str() != "block_argument" {
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
|
-
let symbol = send_node::
|
|
203
|
+
let symbol = send_node::named_children_of(block_pass, context);
|
|
204
204
|
if !matches!(symbol.as_slice(), [only] if send_node::symbol_name(*only, context).is_some()) {
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
@@ -17,7 +17,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
17
17
|
.unwrap_or(3) as usize;
|
|
18
18
|
|
|
19
19
|
for node in context.nodes_of("case") {
|
|
20
|
-
let branches = super::nodes::
|
|
20
|
+
let branches = super::nodes::children_in(node, context);
|
|
21
21
|
let whens: Vec<Node<'_>> = branches
|
|
22
22
|
.iter()
|
|
23
23
|
.copied()
|
|
@@ -33,7 +33,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
33
33
|
let mut conditions = Vec::new();
|
|
34
34
|
let mut bodies = Vec::new();
|
|
35
35
|
for when in &whens {
|
|
36
|
-
let children = super::nodes::
|
|
36
|
+
let children = super::nodes::children_in(*when, context);
|
|
37
37
|
let [pattern, body] = children.as_slice() else {
|
|
38
38
|
conditions.clear();
|
|
39
39
|
break;
|
|
@@ -42,8 +42,8 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
42
42
|
conditions.clear();
|
|
43
43
|
break;
|
|
44
44
|
}
|
|
45
|
-
let patterns = super::nodes::
|
|
46
|
-
let statements = super::nodes::
|
|
45
|
+
let patterns = super::nodes::children_in(*pattern, context);
|
|
46
|
+
let statements = super::nodes::children_in(*body, context);
|
|
47
47
|
let ([condition], [statement]) = (patterns.as_slice(), statements.as_slice()) else {
|
|
48
48
|
conditions.clear();
|
|
49
49
|
break;
|
|
@@ -88,14 +88,14 @@ fn recursive_basic_literal(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
|
88
88
|
.is_some_and(|operator| {
|
|
89
89
|
RECURSIVE_METHODS.contains(&context.source.node_text(operator))
|
|
90
90
|
})
|
|
91
|
-
&& super::nodes::
|
|
91
|
+
&& super::nodes::children_in(node, context)
|
|
92
92
|
.into_iter()
|
|
93
93
|
.all(|child| recursive_basic_literal(context, child))
|
|
94
94
|
}
|
|
95
95
|
"call" => {
|
|
96
96
|
node.field("method")
|
|
97
97
|
.is_some_and(|method| RECURSIVE_METHODS.contains(&context.source.node_text(method)))
|
|
98
|
-
&& super::nodes::
|
|
98
|
+
&& super::nodes::children_in(node, context)
|
|
99
99
|
.into_iter()
|
|
100
100
|
.all(|child| recursive_basic_literal(context, child))
|
|
101
101
|
}
|
|
@@ -107,10 +107,10 @@ fn recursive_basic_literal(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
|
107
107
|
| "regex"
|
|
108
108
|
| "subshell"
|
|
109
109
|
| "boolean"
|
|
110
|
-
| "parenthesized_statements" => super::nodes::
|
|
110
|
+
| "parenthesized_statements" => super::nodes::children_in(node, context)
|
|
111
111
|
.into_iter()
|
|
112
112
|
.all(|child| recursive_basic_literal(context, child)),
|
|
113
|
-
"string" | "delimited_symbol" if is_interpolated(node) => super::nodes::
|
|
113
|
+
"string" | "delimited_symbol" if is_interpolated(node) => super::nodes::children_in(node, context)
|
|
114
114
|
.into_iter()
|
|
115
115
|
.all(|child| recursive_basic_literal(context, child)),
|
|
116
116
|
_ => matches!(upstream_type(context, node), Some(name) if BASIC_LITERALS.contains(&name)),
|
|
@@ -53,7 +53,7 @@ fn bracket_offense(
|
|
|
53
53
|
if node.field("block").is_some() || !send_node::is_plain_send(node, context) {
|
|
54
54
|
return None;
|
|
55
55
|
}
|
|
56
|
-
let arguments = super::nodes::
|
|
56
|
+
let arguments = super::nodes::children_in(node.field("arguments")?, context);
|
|
57
57
|
let [key] = arguments.as_slice() else {
|
|
58
58
|
return None;
|
|
59
59
|
};
|
|
@@ -75,10 +75,16 @@ fn bracket_offense(
|
|
|
75
75
|
|
|
76
76
|
/// `offense_for_fetch?`: a one-argument `[]`, in either of the two spellings the grammar has for it.
|
|
77
77
|
fn fetch_offense(node: Node<'_>, context: &RuleContext<'_>) -> Option<Offense> {
|
|
78
|
+
// `node.method?(:[])`: written on the left of an assignment the same brackets are `[]=`, which
|
|
79
|
+
// `fetch` has no counterpart for. The grammar spells both as an `element_reference`, so the
|
|
80
|
+
// parent has to be asked which one this is.
|
|
81
|
+
if is_assignment_target(node, context) {
|
|
82
|
+
return None;
|
|
83
|
+
}
|
|
78
84
|
let (key, selector_start, safe_navigation) = match node.kind_str() {
|
|
79
85
|
"element_reference" => {
|
|
80
86
|
let object = node.field("object")?;
|
|
81
|
-
let indices = super::nodes::
|
|
87
|
+
let indices = super::nodes::children_in(node, context);
|
|
82
88
|
let [_, key] = indices.as_slice() else {
|
|
83
89
|
return None;
|
|
84
90
|
};
|
|
@@ -91,7 +97,7 @@ fn fetch_offense(node: Node<'_>, context: &RuleContext<'_>) -> Option<Offense> {
|
|
|
91
97
|
if context.source.node_text(selector) != "[]" {
|
|
92
98
|
return None;
|
|
93
99
|
}
|
|
94
|
-
let arguments = super::nodes::
|
|
100
|
+
let arguments = super::nodes::children_in(node.field("arguments")?, context);
|
|
95
101
|
let [key] = arguments.as_slice() else {
|
|
96
102
|
return None;
|
|
97
103
|
};
|
|
@@ -142,3 +148,37 @@ fn receiver_name(receiver: Node<'_>, context: &RuleContext<'_>) -> String {
|
|
|
142
148
|
}
|
|
143
149
|
context.source.node_text(receiver).to_owned()
|
|
144
150
|
}
|
|
151
|
+
|
|
152
|
+
/// Whether the node stands on the left of an assignment, where its brackets mean `[]=`.
|
|
153
|
+
///
|
|
154
|
+
/// Only a plain assignment counts. `x[k] += 1` is `(op-asgn (send x :[] k) :+ 1)` upstream -- the
|
|
155
|
+
/// read is a `send :[]` of its own, and the cop reports it -- while `x[k] = 1` is a single
|
|
156
|
+
/// `send :[]=` with no read in it at all. A multiple assignment writes through `[]=` as well, so
|
|
157
|
+
/// its target list is excluded too.
|
|
158
|
+
fn is_assignment_target(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
159
|
+
let Some(parent) = node.parent_of(context) else {
|
|
160
|
+
return false;
|
|
161
|
+
};
|
|
162
|
+
match parent.kind_str() {
|
|
163
|
+
// The grammar also spells `value[index] =~ /pattern/` as an `assignment`: `=` is the
|
|
164
|
+
// assignment token and `~ /pattern/` its unary right side. With no whitespace between
|
|
165
|
+
// those two characters Ruby reads the pair as the match operator, so the brackets remain
|
|
166
|
+
// a `send :[]` upstream and must not be mistaken for an `[]=` target.
|
|
167
|
+
"assignment" => {
|
|
168
|
+
if parent.field("left") != Some(node) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
let match_operator = parent.field("right").is_some_and(|right| {
|
|
172
|
+
right.kind_str() == "unary"
|
|
173
|
+
&& right
|
|
174
|
+
.field("operator")
|
|
175
|
+
.is_some_and(|operator| context.source.node_text(operator) == "~")
|
|
176
|
+
&& right.start_byte() > 0
|
|
177
|
+
&& context.source.text().as_bytes()[right.start_byte() - 1] == b'='
|
|
178
|
+
});
|
|
179
|
+
!match_operator
|
|
180
|
+
}
|
|
181
|
+
"left_assignment_list" | "rest_assignment" => true,
|
|
182
|
+
_ => false,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -50,20 +50,26 @@ pub(super) fn check(
|
|
|
50
50
|
if !arguments(node).is_empty() {
|
|
51
51
|
continue;
|
|
52
52
|
}
|
|
53
|
-
let parameters = super::nodes::
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
let parameters = super::nodes::children_in(
|
|
54
|
+
match block.field("parameters") {
|
|
55
|
+
Some(parameters) => parameters,
|
|
56
|
+
None => continue,
|
|
57
|
+
},
|
|
58
|
+
context,
|
|
59
|
+
);
|
|
57
60
|
let [key, value] = parameters.as_slice() else {
|
|
58
61
|
continue;
|
|
59
62
|
};
|
|
60
63
|
if key.kind_str() != "identifier" || value.kind_str() != "identifier" {
|
|
61
64
|
continue;
|
|
62
65
|
}
|
|
63
|
-
let body = super::nodes::
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
let body = super::nodes::children_in(
|
|
67
|
+
match block.field("body") {
|
|
68
|
+
Some(body) => body,
|
|
69
|
+
None => continue,
|
|
70
|
+
},
|
|
71
|
+
context,
|
|
72
|
+
);
|
|
67
73
|
let [statement] = body.as_slice() else {
|
|
68
74
|
continue;
|
|
69
75
|
};
|
|
@@ -221,13 +227,13 @@ fn semantically_except(selector: &str, test: &Test<'_>) -> bool {
|
|
|
221
227
|
/// `except_key_source`: the keys as the replacement writes them out.
|
|
222
228
|
fn key_source(key: Node<'_>, context: &RuleContext<'_>) -> String {
|
|
223
229
|
match key.kind_str() {
|
|
224
|
-
"array" => super::nodes::
|
|
230
|
+
"array" => super::nodes::children_in(key, context)
|
|
225
231
|
.iter()
|
|
226
232
|
.map(|value| context.source.node_text(*value).to_owned())
|
|
227
233
|
.collect::<Vec<_>>()
|
|
228
234
|
.join(", "),
|
|
229
235
|
// `percent_literal?`: the elements are written bare, so each is spelled back as a literal.
|
|
230
|
-
"string_array" | "symbol_array" => super::nodes::
|
|
236
|
+
"string_array" | "symbol_array" => super::nodes::children_in(key, context)
|
|
231
237
|
.iter()
|
|
232
238
|
.map(|value| decorate_source(*value, context))
|
|
233
239
|
.collect::<Vec<_>>()
|