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
|
@@ -17,7 +17,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
17
17
|
continue;
|
|
18
18
|
};
|
|
19
19
|
// `empty_and_without_delimiters?`: bars were written around nothing.
|
|
20
|
-
if !super::nodes::
|
|
20
|
+
if !super::nodes::children_in(parameters, context).is_empty() {
|
|
21
21
|
continue;
|
|
22
22
|
}
|
|
23
23
|
let Some(begin) = block.child(0) else {
|
|
@@ -15,7 +15,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
15
15
|
if is_unsupported_parent(context, node) {
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
|
-
let branches = super::nodes::
|
|
18
|
+
let branches = super::nodes::children_in(node, context);
|
|
19
19
|
let whens: Vec<Node<'_>> = branches
|
|
20
20
|
.iter()
|
|
21
21
|
.copied()
|
|
@@ -29,7 +29,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
29
29
|
.iter()
|
|
30
30
|
.filter_map(|when| when.field("body"))
|
|
31
31
|
.chain(branches.iter().copied().filter(|it| it.kind_str() == "else"));
|
|
32
|
-
if bodies.into_iter().any(holds_return) {
|
|
32
|
+
if bodies.into_iter().any(|body| holds_return(body, context)) {
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
let (Some(case_keyword), Some(when_keyword)) = (node.child(0), first_when.child(0)) else {
|
|
@@ -109,9 +109,9 @@ fn is_unsupported_parent(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
fn holds_return(body: Node<'_>) -> bool {
|
|
112
|
+
fn holds_return(body: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
113
113
|
let mut found = false;
|
|
114
|
-
walk_named(body, &mut |node| {
|
|
114
|
+
walk_named(body, context, &mut |node| {
|
|
115
115
|
found = found || node.kind_str() == "return";
|
|
116
116
|
});
|
|
117
117
|
found
|
|
@@ -148,7 +148,7 @@ fn correct_when_conditions(
|
|
|
148
148
|
// the `case`, because the node it reaches for is not there.
|
|
149
149
|
let nested = has_upstream_parent(case_node);
|
|
150
150
|
for when in whens {
|
|
151
|
-
let conditions: Vec<Node<'_>> = super::nodes::
|
|
151
|
+
let conditions: Vec<Node<'_>> = super::nodes::children_in(*when, context)
|
|
152
152
|
.into_iter()
|
|
153
153
|
.filter(|child| child.kind_str() == "pattern")
|
|
154
154
|
.collect();
|
|
@@ -191,7 +191,7 @@ fn then_keyword<'tree>(when: Node<'tree>) -> Option<Node<'tree>> {
|
|
|
191
191
|
/// once the conditions are joined with one.
|
|
192
192
|
fn parenthesize(context: &RuleContext<'_>, condition: Node<'_>) -> String {
|
|
193
193
|
let source = context.source.node_text(condition);
|
|
194
|
-
let inner = super::nodes::
|
|
194
|
+
let inner = super::nodes::children_in(condition, context);
|
|
195
195
|
let binds_looser = match inner.as_slice() {
|
|
196
196
|
[only] => match only.kind_str() {
|
|
197
197
|
"if" | "unless" | "if_modifier" | "unless_modifier" | "conditional" | "range" => true,
|
|
@@ -39,7 +39,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
39
39
|
let Some(keyword) = clause.child(0) else {
|
|
40
40
|
continue;
|
|
41
41
|
};
|
|
42
|
-
let body = super::nodes::
|
|
42
|
+
let body = super::nodes::children_in(clause, context);
|
|
43
43
|
let reportable = match body.as_slice() {
|
|
44
44
|
[] => empty_style,
|
|
45
45
|
[only] => nil_style && only.kind_str() == "nil",
|
|
@@ -15,10 +15,16 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
15
15
|
if context.source.node_text(node).contains('`') {
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
|
+
// **An empty delimiter leaves upstream with nothing to point at.** `<<''` parses to a
|
|
19
|
+
// `dstr` with no children and no expression range, so `add_offense` raises a `TypeError`
|
|
20
|
+
// the runner swallows and the cop reports nothing. Silence is what upstream does here.
|
|
21
|
+
if has_empty_delimiter(node, context) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
18
24
|
let Some(body) = heredoc_body(node, context) else {
|
|
19
25
|
continue;
|
|
20
26
|
};
|
|
21
|
-
let Some(terminator) = super::nodes::
|
|
27
|
+
let Some(terminator) = super::nodes::children_in(body, context)
|
|
22
28
|
.into_iter()
|
|
23
29
|
.find(|child| child.kind_str() == "heredoc_end")
|
|
24
30
|
else {
|
|
@@ -49,6 +55,13 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
/// Whether the opener names its terminator with an empty quoted string, as `<<''` does.
|
|
59
|
+
fn has_empty_delimiter(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
60
|
+
let text = context.source.node_text(node);
|
|
61
|
+
let delimiter = text.trim_start_matches('<').trim_start_matches(['~', '-']);
|
|
62
|
+
matches!(delimiter, "''" | "\"\"")
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
/// `node.loc.heredoc_body.source.empty?`.
|
|
53
66
|
///
|
|
54
67
|
/// The grammar's body starts where the opener was written rather than on the line below, and it runs
|
|
@@ -10,7 +10,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
10
10
|
continue;
|
|
11
11
|
};
|
|
12
12
|
// `empty_and_without_delimiters?`: parentheses were written around nothing.
|
|
13
|
-
if !super::nodes::
|
|
13
|
+
if !super::nodes::children_in(parameters, context).is_empty() {
|
|
14
14
|
continue;
|
|
15
15
|
}
|
|
16
16
|
// `send_node.source_range.end_pos`: the end of the `->` the parameters follow.
|
|
@@ -73,7 +73,7 @@ impl Literal {
|
|
|
73
73
|
match node.kind_str() {
|
|
74
74
|
// `Array[]` and `Hash[]`, which upstream reads as a call to `:[]` with no arguments.
|
|
75
75
|
"element_reference" => {
|
|
76
|
-
if super::nodes::
|
|
76
|
+
if super::nodes::children_in(node, context).len() != 1 {
|
|
77
77
|
return None;
|
|
78
78
|
}
|
|
79
79
|
let object = node.field("object")?;
|
|
@@ -110,6 +110,13 @@ impl Literal {
|
|
|
110
110
|
if literal != Self::String && node.field("block").is_some() {
|
|
111
111
|
return None;
|
|
112
112
|
}
|
|
113
|
+
// **`hash_with_block(node.parent)` reaches past the call's own block.**
|
|
114
|
+
// A `Hash.new` written as the body of another `Hash.new`'s block has that
|
|
115
|
+
// block for a parent upstream, so the check meant for the outer call
|
|
116
|
+
// silences the inner one too.
|
|
117
|
+
if literal == Self::Hash && inside_hash_new_block(node, context) {
|
|
118
|
+
return None;
|
|
119
|
+
}
|
|
113
120
|
Some(literal)
|
|
114
121
|
}
|
|
115
122
|
// `Array([])` and `Hash([])`, the conversion functions.
|
|
@@ -135,6 +142,31 @@ impl Literal {
|
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
/// `(const {nil? cbase} :Array)` and its `Hash` and `String` counterparts.
|
|
145
|
+
/// Whether the call stands as the body of a `Hash.new { ... }` block, which is what
|
|
146
|
+
/// `hash_with_block(node.parent)` answers to for a nested call.
|
|
147
|
+
fn inside_hash_new_block(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
148
|
+
let Some(body) = node.parent_of(context) else {
|
|
149
|
+
return false;
|
|
150
|
+
};
|
|
151
|
+
let Some(block) = body
|
|
152
|
+
.parent_of(context)
|
|
153
|
+
.filter(|parent| matches!(parent.kind_str(), "block" | "do_block"))
|
|
154
|
+
.or_else(|| matches!(body.kind_str(), "block" | "do_block").then_some(body))
|
|
155
|
+
else {
|
|
156
|
+
return false;
|
|
157
|
+
};
|
|
158
|
+
block
|
|
159
|
+
.parent_of(context)
|
|
160
|
+
.filter(|call| call.kind_str() == "call")
|
|
161
|
+
.is_some_and(|call| {
|
|
162
|
+
call.field("method")
|
|
163
|
+
.is_some_and(|method| context.source.node_text(method) == "new")
|
|
164
|
+
&& call
|
|
165
|
+
.field("receiver")
|
|
166
|
+
.is_some_and(|receiver| context.source.node_text(receiver) == "Hash")
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
138
170
|
fn named_constant(node: Node<'_>, context: &RuleContext<'_>) -> Option<Literal> {
|
|
139
171
|
for (name, literal) in [
|
|
140
172
|
("Array", Literal::Array),
|
|
@@ -150,7 +182,7 @@ fn named_constant(node: Node<'_>, context: &RuleContext<'_>) -> Option<Literal>
|
|
|
150
182
|
|
|
151
183
|
fn is_empty_array(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
152
184
|
node.kind_str() == "array"
|
|
153
|
-
&& super::nodes::
|
|
185
|
+
&& super::nodes::children_in(node, context).is_empty()
|
|
154
186
|
&& context.source.node_text(node).starts_with('[')
|
|
155
187
|
}
|
|
156
188
|
|
|
@@ -89,7 +89,7 @@ fn parameters(context: &RuleContext<'_>, node: Node<'_>) -> String {
|
|
|
89
89
|
let Some(list) = node.field("parameters") else {
|
|
90
90
|
return String::new();
|
|
91
91
|
};
|
|
92
|
-
let sources: Vec<&str> = super::nodes::
|
|
92
|
+
let sources: Vec<&str> = super::nodes::children_in(list, context)
|
|
93
93
|
.into_iter()
|
|
94
94
|
.map(|parameter| context.source.node_text(parameter))
|
|
95
95
|
.collect();
|
|
@@ -14,7 +14,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
14
14
|
.setting::<String>("EnforcedStyle")
|
|
15
15
|
.is_some_and(|style| style == "ternary");
|
|
16
16
|
for node in context.nodes_of("interpolation") {
|
|
17
|
-
for child in super::nodes::
|
|
17
|
+
for child in super::nodes::children_in(node, context) {
|
|
18
18
|
if ternary_style {
|
|
19
19
|
ternary_correction(context, node, child, offenses);
|
|
20
20
|
} else {
|
data/src/rules/style/endless.rs
CHANGED
|
@@ -37,10 +37,18 @@ pub(super) fn receiver(context: &RuleContext<'_>, node: Node<'_>) -> String {
|
|
|
37
37
|
format!("{}{operator}", context.source.node_text(object))
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/// `arguments`:
|
|
40
|
+
/// `arguments`: `node.arguments.any? ? node.arguments.source : ''`.
|
|
41
|
+
///
|
|
42
|
+
/// Upstream asks the list what it holds, not whether it was written, so an empty `()` is dropped:
|
|
43
|
+
/// `def foo() = x` rewrites to `def foo`, not `def foo()`.
|
|
41
44
|
pub(super) fn arguments<'a>(context: &'a RuleContext<'_>, node: Node<'_>) -> &'a str {
|
|
42
|
-
node.field("parameters")
|
|
43
|
-
|
|
45
|
+
let Some(parameters) = node.field("parameters") else {
|
|
46
|
+
return "";
|
|
47
|
+
};
|
|
48
|
+
match super::nodes::children_in(parameters, context).is_empty() {
|
|
49
|
+
true => "",
|
|
50
|
+
false => context.source.node_text(parameters),
|
|
51
|
+
}
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
/// `configured_indentation_width`.
|
data/src/rules/style/env_home.rs
CHANGED
|
@@ -24,7 +24,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
24
24
|
if is_assignment_target(node, context) {
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
|
-
let parts = super::nodes::
|
|
27
|
+
let parts = super::nodes::children_in(node, context);
|
|
28
28
|
let [object, index] = parts.as_slice() else {
|
|
29
29
|
continue;
|
|
30
30
|
};
|
|
@@ -354,6 +354,12 @@ fn arguments<'tree>(call: Node<'tree>) -> Vec<Arg<'tree>> {
|
|
|
354
354
|
let mut out = Vec::new();
|
|
355
355
|
for argument in send_node::arguments(call) {
|
|
356
356
|
let (node, range) = (argument.first(), argument.range());
|
|
357
|
+
// **A heredoc's body is written inside the argument list here.** Upstream keeps it under
|
|
358
|
+
// the opening marker, so the body is no argument of its own -- reading it as the last one
|
|
359
|
+
// made `__LINE__ + 1` invisible and quoted the whole body back at the user.
|
|
360
|
+
if node.kind_str() == "heredoc_body" {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
357
363
|
let folded = (argument.parts().len() == 1 && node.kind_str() == "assignment")
|
|
358
364
|
.then(|| node.field("left"))
|
|
359
365
|
.flatten()
|
data/src/rules/style/even_odd.rs
CHANGED
|
@@ -21,7 +21,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
21
21
|
};
|
|
22
22
|
// `{(send $_ :% (int 2)) (begin (send $_ :% (int 2)))}`.
|
|
23
23
|
let modulo = match left.kind_str() {
|
|
24
|
-
"parenthesized_statements" => match super::nodes::
|
|
24
|
+
"parenthesized_statements" => match super::nodes::children_in(left, context).as_slice() {
|
|
25
25
|
[only] => *only,
|
|
26
26
|
_ => continue,
|
|
27
27
|
},
|
|
@@ -83,7 +83,7 @@ fn match_call<'tree>(
|
|
|
83
83
|
/// sequences, so what is left is checking that the run between them holds nothing a regexp engine
|
|
84
84
|
/// would read as syntax, and that no flag was written after the closing delimiter.
|
|
85
85
|
fn exact_match_text<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> Option<&'a str> {
|
|
86
|
-
let parts = super::nodes::
|
|
86
|
+
let parts = super::nodes::children_in(node, context);
|
|
87
87
|
let [start, literal, end] = parts.as_slice() else {
|
|
88
88
|
return None;
|
|
89
89
|
};
|
|
@@ -77,7 +77,7 @@ fn report_file(
|
|
|
77
77
|
if context.source.node_text(default_dir) != "__FILE__"
|
|
78
78
|
|| path.kind_str() != "string"
|
|
79
79
|
|| path.start_position().row != path.end_position().row
|
|
80
|
-
|| super::nodes::
|
|
80
|
+
|| super::nodes::children_in(path, context)
|
|
81
81
|
.iter()
|
|
82
82
|
.any(|child| child.kind_str() == "interpolation")
|
|
83
83
|
{
|
|
@@ -158,7 +158,7 @@ fn file_expand_path<'tree>(
|
|
|
158
158
|
/// `Pathname(x).parent.expand_path` and `Pathname.new(x).parent.expand_path`: the `.parent` call,
|
|
159
159
|
/// the argument, and which of the two spellings it was.
|
|
160
160
|
fn pathname_parent<'tree>(
|
|
161
|
-
context: &RuleContext<'_>,
|
|
161
|
+
context: &'tree RuleContext<'_>,
|
|
162
162
|
node: Node<'tree>,
|
|
163
163
|
) -> Option<(Node<'tree>, Node<'tree>, bool)> {
|
|
164
164
|
if node.field("arguments").is_some() {
|
|
@@ -178,7 +178,7 @@ fn pathname_parent<'tree>(
|
|
|
178
178
|
return None;
|
|
179
179
|
}
|
|
180
180
|
let arguments = call.field("arguments")?;
|
|
181
|
-
let [argument] = super::nodes::
|
|
181
|
+
let [argument] = super::nodes::children_in(arguments, context)[..] else {
|
|
182
182
|
return None;
|
|
183
183
|
};
|
|
184
184
|
let method = call.field("method")?;
|
|
@@ -23,7 +23,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
23
23
|
.map(super::nodes::children)
|
|
24
24
|
.unwrap_or_default();
|
|
25
25
|
// The grammar hangs a `yield`'s arguments off it without a field name.
|
|
26
|
-
let yield_arguments = super::nodes::
|
|
26
|
+
let yield_arguments = super::nodes::children_in(node, context)
|
|
27
27
|
.into_iter()
|
|
28
28
|
.find(|child| child.kind_str() == "argument_list")
|
|
29
29
|
.map(super::nodes::children)
|
|
@@ -92,7 +92,7 @@ fn yielding_block<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option
|
|
|
92
92
|
if !matches!(body.kind_str(), "block_body" | "body_statement") {
|
|
93
93
|
return None;
|
|
94
94
|
}
|
|
95
|
-
match super::nodes::
|
|
95
|
+
match super::nodes::children_in(body, context).as_slice() {
|
|
96
96
|
[only] if only.id() == node.id() => {}
|
|
97
97
|
_ => return None,
|
|
98
98
|
}
|
|
@@ -193,7 +193,7 @@ fn zsuper_arguments(
|
|
|
193
193
|
if call.field("method")?.kind_str() != "super" {
|
|
194
194
|
return None;
|
|
195
195
|
}
|
|
196
|
-
let parameters = super::nodes::
|
|
196
|
+
let parameters = super::nodes::children_in(definition.field("parameters")?, context);
|
|
197
197
|
let written: Vec<String> = parameters
|
|
198
198
|
.into_iter()
|
|
199
199
|
.map(|parameter| match parameter.kind_str() {
|
|
@@ -19,6 +19,11 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
19
19
|
{
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
|
+
// `on_send` is never called for a `csend`, and this cop does not alias `on_csend`, so
|
|
23
|
+
// `File&.open` is not its business. The grammar spells both as `call`.
|
|
24
|
+
if !crate::rules::send_node::is_plain_send(node, context) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
22
27
|
// `node.block_argument?`: `File.open(path, &:read)` hands the descriptor to something that
|
|
23
28
|
// closes it.
|
|
24
29
|
if arguments(node)
|
|
@@ -53,7 +53,7 @@ pub(super) fn parse<'a>(
|
|
|
53
53
|
if node.kind_str() != "block_argument" {
|
|
54
54
|
return None;
|
|
55
55
|
}
|
|
56
|
-
let symbol = super::nodes::
|
|
56
|
+
let symbol = super::nodes::children_in(node, context);
|
|
57
57
|
let [symbol] = symbol.as_slice() else {
|
|
58
58
|
return None;
|
|
59
59
|
};
|
|
@@ -77,14 +77,14 @@ pub(super) fn block_calls<'tree>(
|
|
|
77
77
|
arity: usize,
|
|
78
78
|
) -> Option<Vec<Range<usize>>> {
|
|
79
79
|
let block = node.field("block")?;
|
|
80
|
-
let parameters = super::nodes::
|
|
80
|
+
let parameters = super::nodes::children_in(block.field("parameters")?, context);
|
|
81
81
|
let [parameter] = parameters.as_slice() else {
|
|
82
82
|
return None;
|
|
83
83
|
};
|
|
84
84
|
if parameter.kind_str() != "identifier" {
|
|
85
85
|
return None;
|
|
86
86
|
}
|
|
87
|
-
let body = super::nodes::
|
|
87
|
+
let body = super::nodes::children_in(block.field("body")?, context);
|
|
88
88
|
let [statement] = body.as_slice() else {
|
|
89
89
|
return None;
|
|
90
90
|
};
|
|
@@ -109,7 +109,7 @@ fn removed_heredocs(
|
|
|
109
109
|
let Some(body) = crate::rules::send_node::heredoc_body(opener, context) else {
|
|
110
110
|
continue;
|
|
111
111
|
};
|
|
112
|
-
let parts = super::nodes::
|
|
112
|
+
let parts = super::nodes::children_in(body, context);
|
|
113
113
|
let Some(terminator) = parts.iter().find(|part| part.kind_str() == "heredoc_end") else {
|
|
114
114
|
continue;
|
|
115
115
|
};
|
data/src/rules/style/for.rs
CHANGED
|
@@ -33,7 +33,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
33
33
|
fn for_to_each(context: &RuleContext<'_>, node: Node<'_>) -> Option<Offense> {
|
|
34
34
|
let variable = node.field("pattern")?;
|
|
35
35
|
let value = node.field("value")?;
|
|
36
|
-
let collection = *super::nodes::
|
|
36
|
+
let collection = *super::nodes::children_in(value, context).first()?;
|
|
37
37
|
let body = node.field("body")?;
|
|
38
38
|
// `for_node.do?`: the head ends at the `do` when there is one, and at the collection otherwise.
|
|
39
39
|
let end = match body.child(0).filter(|first| first.kind_str() == "do") {
|
|
@@ -24,6 +24,9 @@ pub(super) struct Sequence {
|
|
|
24
24
|
pub flags: String,
|
|
25
25
|
pub width: String,
|
|
26
26
|
pub precision: String,
|
|
27
|
+
/// Whether a `.` was written at all. `%.d` is a precision of **zero**, which prints no digits,
|
|
28
|
+
/// and an empty `precision` cannot tell that apart from a sequence with no precision.
|
|
29
|
+
pub has_precision: bool,
|
|
27
30
|
pub name: Option<String>,
|
|
28
31
|
/// The type character, absent from a template sequence.
|
|
29
32
|
pub kind: Option<char>,
|
|
@@ -65,6 +68,7 @@ fn match_at(text: &str, bytes: &[u8], start: usize) -> Option<Sequence> {
|
|
|
65
68
|
flags: String::new(),
|
|
66
69
|
width: String::new(),
|
|
67
70
|
precision: String::new(),
|
|
71
|
+
has_precision: false,
|
|
68
72
|
name: None,
|
|
69
73
|
kind: Some('%'),
|
|
70
74
|
style: SequenceStyle::Percent,
|
|
@@ -204,12 +208,14 @@ fn build(
|
|
|
204
208
|
Some(_) if source.contains('{') => SequenceStyle::Template,
|
|
205
209
|
_ => SequenceStyle::Unannotated,
|
|
206
210
|
};
|
|
211
|
+
let has_precision = source.contains('.');
|
|
207
212
|
Sequence {
|
|
208
213
|
begin,
|
|
209
214
|
end,
|
|
210
215
|
flags,
|
|
211
216
|
width,
|
|
212
217
|
precision,
|
|
218
|
+
has_precision,
|
|
213
219
|
name,
|
|
214
220
|
kind,
|
|
215
221
|
style,
|
|
@@ -6,8 +6,8 @@ use tree_sitter::Node;
|
|
|
6
6
|
|
|
7
7
|
use crate::diagnostic::{Edit, Offense};
|
|
8
8
|
use crate::rules::RuleContext;
|
|
9
|
-
use crate::rules::send_node::{Argument, arguments, is_plain_send};
|
|
10
9
|
use crate::rules::node_ext::NodeExt;
|
|
10
|
+
use crate::rules::send_node::{Argument, arguments, is_plain_send};
|
|
11
11
|
|
|
12
12
|
/// Literal kinds a leading sign belongs to rather than turning into a `:-@` call.
|
|
13
13
|
const NUMBER_KINDS: &[&str] = &["integer", "float", "rational", "complex"];
|
|
@@ -68,6 +68,8 @@ struct Formatter<'tree> {
|
|
|
68
68
|
receiver: Option<Range<usize>>,
|
|
69
69
|
/// `node.first_argument`, which is all either correction reads.
|
|
70
70
|
argument: Option<Operand<'tree>>,
|
|
71
|
+
/// `node.arguments` of a `format` / `sprintf` call, which only the `percent` correction reads.
|
|
72
|
+
written: Vec<Argument<'tree>>,
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
impl<'tree> Formatter<'tree> {
|
|
@@ -99,6 +101,7 @@ impl<'tree> Formatter<'tree> {
|
|
|
99
101
|
span: node.byte_range(),
|
|
100
102
|
receiver: Some(left.byte_range()),
|
|
101
103
|
argument: Some(Operand::Node(right)),
|
|
104
|
+
written: Vec::new(),
|
|
102
105
|
})
|
|
103
106
|
}
|
|
104
107
|
|
|
@@ -125,6 +128,7 @@ impl<'tree> Formatter<'tree> {
|
|
|
125
128
|
span: node.byte_range(),
|
|
126
129
|
receiver: Some(first.byte_range()),
|
|
127
130
|
argument: Some(Operand::Text(second.start_byte() + 1..second.end_byte())),
|
|
131
|
+
written: Vec::new(),
|
|
128
132
|
})
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -148,6 +152,7 @@ impl<'tree> Formatter<'tree> {
|
|
|
148
152
|
span: node.byte_range(),
|
|
149
153
|
receiver: Some(receiver.byte_range()),
|
|
150
154
|
argument: first.map(Operand::Node),
|
|
155
|
+
written: Vec::new(),
|
|
151
156
|
});
|
|
152
157
|
}
|
|
153
158
|
// `(send nil? ${:sprintf :format} _ _ ...)`: no receiver and at least two arguments.
|
|
@@ -157,7 +162,8 @@ impl<'tree> Formatter<'tree> {
|
|
|
157
162
|
{
|
|
158
163
|
return None;
|
|
159
164
|
}
|
|
160
|
-
|
|
165
|
+
let written = arguments(node);
|
|
166
|
+
if written.len() < 2 {
|
|
161
167
|
return None;
|
|
162
168
|
}
|
|
163
169
|
Some(Self {
|
|
@@ -166,15 +172,14 @@ impl<'tree> Formatter<'tree> {
|
|
|
166
172
|
span: node.byte_range(),
|
|
167
173
|
receiver: None,
|
|
168
174
|
argument: None,
|
|
175
|
+
written,
|
|
169
176
|
})
|
|
170
177
|
}
|
|
171
178
|
}
|
|
172
179
|
|
|
173
180
|
fn correction(context: &RuleContext<'_>, found: &Formatter<'_>, style: &str) -> Option<Edit> {
|
|
174
|
-
// `autocorrect_to_percent` is only reachable with `EnforcedStyle: percent`, which no corpus
|
|
175
|
-
// configures; the offense is reported without a correction rather than guessed at.
|
|
176
181
|
if style == "percent" {
|
|
177
|
-
return
|
|
182
|
+
return to_percent(context, found);
|
|
178
183
|
}
|
|
179
184
|
if found.detected != "percent" {
|
|
180
185
|
return Some(Edit {
|
|
@@ -193,7 +198,7 @@ fn correction(context: &RuleContext<'_>, found: &Formatter<'_>, style: &str) ->
|
|
|
193
198
|
let receiver = found.receiver.clone()?;
|
|
194
199
|
let args = match argument {
|
|
195
200
|
Operand::Node(node) if matches!(node.kind_str(), "array" | "hash") => {
|
|
196
|
-
super::nodes::
|
|
201
|
+
super::nodes::children_in(*node, context)
|
|
197
202
|
.iter()
|
|
198
203
|
.map(|child| context.source.node_text(*child))
|
|
199
204
|
.collect::<Vec<_>>()
|
|
@@ -213,6 +218,67 @@ fn correction(context: &RuleContext<'_>, found: &Formatter<'_>, style: &str) ->
|
|
|
213
218
|
})
|
|
214
219
|
}
|
|
215
220
|
|
|
221
|
+
/// `autocorrect_to_percent`: `format(fmt, a, b)` becomes `fmt % [a, b]`, and a single parameter
|
|
222
|
+
/// stands on its own.
|
|
223
|
+
fn to_percent(context: &RuleContext<'_>, found: &Formatter<'_>) -> Option<Edit> {
|
|
224
|
+
let (format, parameters) = found.written.split_first()?;
|
|
225
|
+
let args = match parameters {
|
|
226
|
+
// A brace-less hash is one `hash` argument upstream, and `format_single_parameter` writes
|
|
227
|
+
// a hash back in braces: `fmt % { a: 1, b: 2 }`.
|
|
228
|
+
[only] if only.parts().len() > 1 || only.first().kind_str() == "pair" => {
|
|
229
|
+
format!("{{ {} }}", context.source.slice(only.range()))
|
|
230
|
+
}
|
|
231
|
+
[only] => format_single_parameter(context, only.first()),
|
|
232
|
+
_ => format!(
|
|
233
|
+
"[{}]",
|
|
234
|
+
parameters
|
|
235
|
+
.iter()
|
|
236
|
+
.map(|argument| context.source.slice(argument.range()).to_owned())
|
|
237
|
+
.collect::<Vec<_>>()
|
|
238
|
+
.join(", ")
|
|
239
|
+
),
|
|
240
|
+
};
|
|
241
|
+
Some(Edit {
|
|
242
|
+
start: found.span.start,
|
|
243
|
+
end: found.span.end,
|
|
244
|
+
replacement: format!("{} % {args}", context.source.slice(format.range())),
|
|
245
|
+
safe: true,
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// `format_single_parameter`.
|
|
250
|
+
fn format_single_parameter(context: &RuleContext<'_>, node: Node<'_>) -> String {
|
|
251
|
+
// `format(fmt, *args)` prints what `fmt % args` does, so the splat comes off.
|
|
252
|
+
if node.kind_str() == "splat_argument" {
|
|
253
|
+
return match super::nodes::children_in(node, context).first() {
|
|
254
|
+
Some(inner) => format_single_parameter(context, *inner),
|
|
255
|
+
None => context.source.node_text(node).to_owned(),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
let source = context.source.node_text(node);
|
|
259
|
+
if node.kind_str() == "hash" {
|
|
260
|
+
return format!("{{ {source} }}");
|
|
261
|
+
}
|
|
262
|
+
match requires_parentheses(context, node) {
|
|
263
|
+
true => format!("({source})"),
|
|
264
|
+
false => source.to_owned(),
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// `requires_parentheses?`: anything binding looser than `%` keeps its meaning only in brackets.
|
|
269
|
+
fn requires_parentheses(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
270
|
+
match node.kind_str() {
|
|
271
|
+
"assignment" | "operator_assignment" | "if" | "unless" | "conditional" | "range" => true,
|
|
272
|
+
// `and` / `or` are `binary` here; so are the operator calls, which need brackets only when
|
|
273
|
+
// written without them -- and an operator call never is.
|
|
274
|
+
"binary" => node.field("operator").is_some_and(|operator| {
|
|
275
|
+
let text = context.source.node_text(operator);
|
|
276
|
+
matches!(text, "&&" | "||" | "and" | "or") || super::nodes::is_operator_method(text)
|
|
277
|
+
}),
|
|
278
|
+
_ => false,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
216
282
|
/// `children.map(&:source).join(', ')` for the array or hash the grammar left as text.
|
|
217
283
|
fn elements_of(source: &str) -> String {
|
|
218
284
|
let inner = source
|
|
@@ -278,11 +344,9 @@ fn is_variable_argument(
|
|
|
278
344
|
.is_some_and(|operand| NUMBER_KINDS.contains(&operand.kind_str()));
|
|
279
345
|
!signed_number && matches!(operator, "-" | "+" | "!" | "~" | "not")
|
|
280
346
|
}
|
|
281
|
-
"binary" => node
|
|
282
|
-
.
|
|
283
|
-
|
|
284
|
-
super::nodes::is_operator_method(context.source.node_text(operator))
|
|
285
|
-
}),
|
|
347
|
+
"binary" => node.field("operator").is_some_and(|operator| {
|
|
348
|
+
super::nodes::is_operator_method(context.source.node_text(operator))
|
|
349
|
+
}),
|
|
286
350
|
_ => false,
|
|
287
351
|
}
|
|
288
352
|
}
|