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
|
@@ -18,66 +18,610 @@ static PLAIN_SYMBOL: LazyLock<Regex> =
|
|
|
18
18
|
/// The quoted form only became a legal hash key in Ruby 2.2.
|
|
19
19
|
const QUOTED_KEY_SINCE: RubyVersion = RubyVersion::new(2, 2);
|
|
20
20
|
|
|
21
|
+
/// `EnforcedShorthandSyntax`: what to do about Ruby 3.1's `{ foo: }`, which is a separate axis
|
|
22
|
+
/// from `EnforcedStyle` and reaches the same pairs.
|
|
23
|
+
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
24
|
+
enum Shorthand {
|
|
25
|
+
Either,
|
|
26
|
+
Always,
|
|
27
|
+
Never,
|
|
28
|
+
Consistent,
|
|
29
|
+
EitherConsistent,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Value omission is Ruby 3.1 syntax. Below that every pair is left alone, whatever the setting
|
|
33
|
+
/// says, because the shorter form would not parse.
|
|
34
|
+
const OMISSION_SINCE: RubyVersion = RubyVersion::new(3, 1);
|
|
35
|
+
|
|
36
|
+
const MSG_19: &str = "Use the new Ruby 1.9 hash syntax.";
|
|
37
|
+
const MSG_HASH_ROCKETS: &str = "Use hash rockets syntax.";
|
|
38
|
+
const MSG_NO_MIXED_KEYS: &str = "Don't mix styles in the same hash.";
|
|
39
|
+
const OMIT_MSG: &str = "Omit the hash value.";
|
|
40
|
+
const EXPLICIT_MSG: &str = "Include the hash value.";
|
|
41
|
+
const MIX_PREFIX: &str = "Do not mix explicit and implicit hash values.";
|
|
42
|
+
|
|
21
43
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
44
|
+
check_shorthand(context, offenses);
|
|
45
|
+
check_syntax(context, offenses);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn check_syntax(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
22
49
|
let style: String = context
|
|
23
50
|
.setting("EnforcedStyle")
|
|
24
51
|
.unwrap_or_else(|| "ruby19".to_owned());
|
|
25
|
-
if style != "ruby19" && style != "ruby19_no_mixed_keys" {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
52
|
let quoted_keys_allowed = context.target_ruby_version() >= QUOTED_KEY_SINCE;
|
|
53
|
+
let prefer_rockets_for_non_alnum = context
|
|
54
|
+
.setting::<bool>("PreferHashRocketsForNonAlnumEndingSymbols")
|
|
55
|
+
.unwrap_or(false);
|
|
29
56
|
|
|
30
|
-
for
|
|
31
|
-
|
|
32
|
-
|
|
57
|
+
for pairs in hash_groups(context) {
|
|
58
|
+
// `sym_indices?`: a hash is left in the old syntax unless every one of its keys can take
|
|
59
|
+
// the new one, so that one rocket that has to stay does not leave the hash written in two
|
|
60
|
+
// styles at once.
|
|
61
|
+
let sym_indices = pairs.iter().all(|pair| {
|
|
62
|
+
word_symbol_pair(
|
|
63
|
+
*pair,
|
|
64
|
+
context,
|
|
65
|
+
quoted_keys_allowed,
|
|
66
|
+
prefer_rockets_for_non_alnum,
|
|
67
|
+
)
|
|
68
|
+
});
|
|
69
|
+
// `force_hash_rockets?`: a hash holding a symbol value is written in the old syntax
|
|
70
|
+
// whatever the style says, so that `{ a: :b }` does not read as two different things.
|
|
71
|
+
let force_hash_rockets = context
|
|
72
|
+
.setting::<bool>("UseHashRocketsWithSymbolValues")
|
|
73
|
+
.unwrap_or(false)
|
|
74
|
+
&& pairs.iter().any(|pair| {
|
|
75
|
+
pair.field("value").is_some_and(|value| {
|
|
76
|
+
matches!(value.kind_str(), "simple_symbol" | "delimited_symbol")
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
// Which delimiter the style rejects, and what it says about it. `check(pairs, delim, msg)`
|
|
80
|
+
// upstream.
|
|
81
|
+
let (reject_colon, message) = match style.as_str() {
|
|
82
|
+
_ if force_hash_rockets => (true, MSG_HASH_ROCKETS),
|
|
83
|
+
"hash_rockets" => (true, MSG_HASH_ROCKETS),
|
|
84
|
+
"ruby19_no_mixed_keys" if sym_indices => (false, MSG_19),
|
|
85
|
+
"ruby19_no_mixed_keys" => (true, MSG_NO_MIXED_KEYS),
|
|
86
|
+
"no_mixed_keys" if sym_indices => {
|
|
87
|
+
// `pairs.first.inverse_delimiter`: whichever way the hash opens, the others have
|
|
88
|
+
// to follow, so what is rejected is the opposite of the first pair's delimiter.
|
|
89
|
+
let first_colon = pair_delimiter(context, pairs[0]).is_some_and(|(_, colon)| colon);
|
|
90
|
+
(!first_colon, MSG_NO_MIXED_KEYS)
|
|
91
|
+
}
|
|
92
|
+
"no_mixed_keys" => (true, MSG_NO_MIXED_KEYS),
|
|
93
|
+
_ if !sym_indices => continue,
|
|
94
|
+
_ => (false, MSG_19),
|
|
33
95
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
96
|
+
|
|
97
|
+
for &node in &pairs {
|
|
98
|
+
let Some((operator, is_colon)) = pair_delimiter(context, node) else {
|
|
99
|
+
continue;
|
|
100
|
+
};
|
|
101
|
+
if is_colon != reject_colon {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if is_colon {
|
|
105
|
+
offenses.push(
|
|
106
|
+
context
|
|
107
|
+
.offense(message, node.start_byte()..operator.end)
|
|
108
|
+
.corrected_by_all(rocket_edits(context, node, &operator)),
|
|
109
|
+
);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let start = node.start_byte();
|
|
114
|
+
// The opening brace is written **into** the rewrite rather than inserted beside it. An
|
|
115
|
+
// insertion at the byte the rewrite starts at is a second edit at the same position,
|
|
116
|
+
// and `apply_edits` refuses a pair like that -- silently, so the cop reads as having
|
|
117
|
+
// declined to correct at all.
|
|
118
|
+
let wrapping = returned_bare_hash(node, context);
|
|
119
|
+
let opening = if wrapping.is_some() { "{" } else { "" };
|
|
120
|
+
// `argument_without_space?`: `foo:bar => 1` has no space between the selector and the
|
|
121
|
+
// hash, and the old syntax did not need one. `foo` + `bar: 1` runs the two together
|
|
122
|
+
// into `foobar: 1`, which is a different program (and here not a program at all).
|
|
123
|
+
let spacing = if argument_without_space(node, context) {
|
|
124
|
+
" "
|
|
125
|
+
} else {
|
|
126
|
+
""
|
|
127
|
+
};
|
|
128
|
+
let mut edits = vec![Edit {
|
|
129
|
+
start,
|
|
130
|
+
end: whitespace_end(context, operator.end),
|
|
131
|
+
replacement: format!("{spacing}{opening}{}: ", key_name(node, context)),
|
|
132
|
+
safe: true,
|
|
133
|
+
}];
|
|
134
|
+
// `corrector.wrap(hash_node, '{', '}')`: `return key: value` is not valid Ruby, so a
|
|
135
|
+
// bare hash handed to `return` has to gain braces as it changes syntax. Upstream does
|
|
136
|
+
// this once per hash, on its first pair. Without it the correction turns working code
|
|
137
|
+
// into a syntax error -- and the reparse guard cannot see it, because each pair is a
|
|
138
|
+
// separate offense.
|
|
139
|
+
if let Some((_, close)) = wrapping {
|
|
140
|
+
edits.push(Edit {
|
|
141
|
+
start: close,
|
|
142
|
+
end: close,
|
|
143
|
+
replacement: "}".to_owned(),
|
|
144
|
+
safe: true,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
offenses.push(
|
|
148
|
+
context
|
|
149
|
+
.offense(message, start..operator.end)
|
|
150
|
+
.corrected_by_all(edits),
|
|
151
|
+
);
|
|
38
152
|
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
39
155
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
156
|
+
/// `autocorrect_hash_rockets`: the key gains the `:` the symbol literal needs and the rocket that
|
|
157
|
+
/// replaces the colon, and the colon itself goes with the space around it.
|
|
158
|
+
fn rocket_edits(
|
|
159
|
+
context: &RuleContext<'_>,
|
|
160
|
+
pair: Node<'_>,
|
|
161
|
+
operator: &std::ops::Range<usize>,
|
|
162
|
+
) -> Vec<Edit> {
|
|
163
|
+
let Some(key) = pair.field("key") else {
|
|
164
|
+
return Vec::new();
|
|
165
|
+
};
|
|
166
|
+
let key_source = context.source.node_text(key);
|
|
167
|
+
let mut replacement = format!(":{key_source} => ");
|
|
168
|
+
// `key_with_hash_rocket += pair_node.key.source if pair_node.value_omission?`: the old syntax
|
|
169
|
+
// has no short form, so the value the colon left out has to be written back.
|
|
170
|
+
if pair.field("value").is_none() {
|
|
171
|
+
replacement.push_str(key_source);
|
|
172
|
+
}
|
|
173
|
+
vec![
|
|
174
|
+
Edit {
|
|
175
|
+
start: key.start_byte(),
|
|
176
|
+
end: key.end_byte(),
|
|
177
|
+
replacement,
|
|
178
|
+
safe: true,
|
|
179
|
+
},
|
|
180
|
+
Edit {
|
|
181
|
+
start: operator.start,
|
|
57
182
|
end: whitespace_end(context, operator.end),
|
|
58
|
-
replacement:
|
|
183
|
+
replacement: String::new(),
|
|
59
184
|
safe: true,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
185
|
+
},
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// The pair's delimiter: its span, and whether it is the colon of the new syntax.
|
|
190
|
+
fn pair_delimiter(
|
|
191
|
+
context: &RuleContext<'_>,
|
|
192
|
+
pair: Node<'_>,
|
|
193
|
+
) -> Option<(std::ops::Range<usize>, bool)> {
|
|
194
|
+
let key = pair.field("key")?;
|
|
195
|
+
let after = key.end_byte();
|
|
196
|
+
let text = context.source.text();
|
|
197
|
+
let end = pair
|
|
198
|
+
.field("value")
|
|
199
|
+
.map_or(pair.end_byte(), |v| v.start_byte());
|
|
200
|
+
let between = text.get(after..end)?;
|
|
201
|
+
if let Some(offset) = between.find("=>") {
|
|
202
|
+
return Some((after + offset..after + offset + "=>".len(), false));
|
|
203
|
+
}
|
|
204
|
+
let offset = between.find(':')?;
|
|
205
|
+
Some((after + offset..after + offset + 1, true))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// `HashShorthandSyntax`: the value-omission axis.
|
|
209
|
+
///
|
|
210
|
+
/// `always` and `never` read one pair at a time -- omit every value that can go, or write every
|
|
211
|
+
/// one back. `consistent` and `either_consistent` read the hash instead, because what they forbid
|
|
212
|
+
/// is the mixture: a hash whose pairs are not all written the same way is reported on the pairs
|
|
213
|
+
/// that break the majority, and which side that is depends on whether some value in the hash
|
|
214
|
+
/// *cannot* be omitted.
|
|
215
|
+
fn check_shorthand(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
216
|
+
let shorthand = match context
|
|
217
|
+
.setting::<String>("EnforcedShorthandSyntax")
|
|
218
|
+
.as_deref()
|
|
219
|
+
{
|
|
220
|
+
Some("always") => Shorthand::Always,
|
|
221
|
+
Some("never") => Shorthand::Never,
|
|
222
|
+
Some("consistent") => Shorthand::Consistent,
|
|
223
|
+
Some("either_consistent") => Shorthand::EitherConsistent,
|
|
224
|
+
_ => Shorthand::Either,
|
|
225
|
+
};
|
|
226
|
+
if shorthand == Shorthand::Either || context.target_ruby_version() < OMISSION_SINCE {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
match shorthand {
|
|
230
|
+
Shorthand::Always | Shorthand::Never => {
|
|
231
|
+
for pair in context.nodes_of("pair") {
|
|
232
|
+
if !in_hash_literal(pair, context) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
on_pair(context, pair, shorthand, offenses);
|
|
236
|
+
}
|
|
72
237
|
}
|
|
73
|
-
|
|
74
|
-
context
|
|
75
|
-
|
|
76
|
-
|
|
238
|
+
_ => {
|
|
239
|
+
for pairs in hash_groups(context) {
|
|
240
|
+
mixed_shorthand(context, &pairs, shorthand, offenses);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// `on_pair` under `always` and `never`.
|
|
247
|
+
fn on_pair(
|
|
248
|
+
context: &RuleContext<'_>,
|
|
249
|
+
pair: Node<'_>,
|
|
250
|
+
shorthand: Shorthand,
|
|
251
|
+
offenses: &mut Vec<Offense>,
|
|
252
|
+
) {
|
|
253
|
+
let Some(key) = pair.field("key") else {
|
|
254
|
+
return;
|
|
255
|
+
};
|
|
256
|
+
let key_source = context.source.node_text(key);
|
|
257
|
+
if shorthand == Shorthand::Always {
|
|
258
|
+
if omits_value(pair) || require_hash_value(context, pair) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
register_shorthand(context, pair, OMIT_MSG, format!("{key_source}:"), offenses);
|
|
262
|
+
} else {
|
|
263
|
+
if !omits_value(pair) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
register_shorthand(
|
|
267
|
+
context,
|
|
268
|
+
pair,
|
|
269
|
+
EXPLICIT_MSG,
|
|
270
|
+
format!("{key_source}: {key_source}"),
|
|
271
|
+
offenses,
|
|
77
272
|
);
|
|
78
273
|
}
|
|
79
274
|
}
|
|
80
275
|
|
|
276
|
+
/// `on_hash_for_mixed_shorthand` and the two checks it dispatches to.
|
|
277
|
+
fn mixed_shorthand(
|
|
278
|
+
context: &RuleContext<'_>,
|
|
279
|
+
pairs: &[Node<'_>],
|
|
280
|
+
shorthand: Shorthand,
|
|
281
|
+
offenses: &mut Vec<Offense>,
|
|
282
|
+
) {
|
|
283
|
+
// `breakdown_value_types_of_hash`.
|
|
284
|
+
let (mut omitted, mut needed, mut omittable) = (Vec::new(), Vec::new(), Vec::new());
|
|
285
|
+
for &pair in pairs {
|
|
286
|
+
if omits_value(pair) {
|
|
287
|
+
omitted.push(pair);
|
|
288
|
+
} else if require_hash_value(context, pair) {
|
|
289
|
+
needed.push(pair);
|
|
290
|
+
} else {
|
|
291
|
+
omittable.push(pair);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
let kinds = usize::from(!omitted.is_empty())
|
|
295
|
+
+ usize::from(!needed.is_empty())
|
|
296
|
+
+ usize::from(!omittable.is_empty());
|
|
297
|
+
|
|
298
|
+
if kinds > 1 {
|
|
299
|
+
// `mixed_shorthand_syntax_check`: a hash holding a value that cannot go is written out in
|
|
300
|
+
// full, and one where every value could go is written short.
|
|
301
|
+
let (targets, message) = if needed.is_empty() {
|
|
302
|
+
(&omittable, format!("{MIX_PREFIX} {OMIT_MSG}"))
|
|
303
|
+
} else {
|
|
304
|
+
(&omitted, format!("{MIX_PREFIX} {EXPLICIT_MSG}"))
|
|
305
|
+
};
|
|
306
|
+
let omit = needed.is_empty();
|
|
307
|
+
for &pair in targets {
|
|
308
|
+
let Some(key) = pair.field("key") else {
|
|
309
|
+
continue;
|
|
310
|
+
};
|
|
311
|
+
let key_source = context.source.node_text(key);
|
|
312
|
+
let replacement = if omit {
|
|
313
|
+
format!("{key_source}:")
|
|
314
|
+
} else {
|
|
315
|
+
format!("{key_source}: {key_source}")
|
|
316
|
+
};
|
|
317
|
+
register_shorthand(context, pair, &message, replacement, offenses);
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// `no_mixed_shorthand_syntax_check`.
|
|
323
|
+
if !needed.is_empty() {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// `ignore_explicit_omissible_hash_shorthand_syntax?`: under `either_consistent` a hash whose
|
|
327
|
+
// values could all be omitted is consistent as it stands, so writing them out is accepted.
|
|
328
|
+
if shorthand == Shorthand::EitherConsistent && omitted.is_empty() {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
for &pair in &omittable {
|
|
332
|
+
let Some(key) = pair.field("key") else {
|
|
333
|
+
continue;
|
|
334
|
+
};
|
|
335
|
+
let key_source = context.source.node_text(key);
|
|
336
|
+
register_shorthand(context, pair, OMIT_MSG, format!("{key_source}:"), offenses);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// `register_offense`: the offense sits on the value -- and on a pair that has none, on the key
|
|
341
|
+
/// the parser puts the implicit value's range over.
|
|
342
|
+
fn register_shorthand(
|
|
343
|
+
context: &RuleContext<'_>,
|
|
344
|
+
pair: Node<'_>,
|
|
345
|
+
message: impl Into<String>,
|
|
346
|
+
replacement: String,
|
|
347
|
+
offenses: &mut Vec<Offense>,
|
|
348
|
+
) {
|
|
349
|
+
let range = match pair.field("value") {
|
|
350
|
+
Some(value) => value.byte_range(),
|
|
351
|
+
None => match pair.field("key") {
|
|
352
|
+
Some(key) => key.byte_range(),
|
|
353
|
+
None => pair.byte_range(),
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
// The closing bracket goes **into** the rewrite: an insertion at the byte the rewrite ends on
|
|
357
|
+
// is a second edit at the same position, which `apply_edits` refuses -- silently, so the cop
|
|
358
|
+
// reads as having declined to correct at all.
|
|
359
|
+
let opening = parentheses_the_omission_needs(context, pair);
|
|
360
|
+
let replacement = match opening.is_some() {
|
|
361
|
+
true => format!("{replacement})"),
|
|
362
|
+
false => replacement,
|
|
363
|
+
};
|
|
364
|
+
let mut edits = vec![Edit {
|
|
365
|
+
start: pair.start_byte(),
|
|
366
|
+
end: pair.end_byte(),
|
|
367
|
+
replacement,
|
|
368
|
+
safe: true,
|
|
369
|
+
}];
|
|
370
|
+
edits.extend(opening);
|
|
371
|
+
offenses.push(
|
|
372
|
+
context
|
|
373
|
+
.offense(message.into(), range)
|
|
374
|
+
.corrected_by_all(edits),
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/// `def_node_that_require_parentheses`: dropping the value from a parenthesis-less call would
|
|
379
|
+
/// leave `foo bar:`, which is not the same program. Upstream adds the parentheses in the same
|
|
380
|
+
/// pass, replacing the space after the selector and closing after the last argument.
|
|
381
|
+
fn parentheses_the_omission_needs(context: &RuleContext<'_>, pair: Node<'_>) -> Option<Edit> {
|
|
382
|
+
let hash = pair.parent_of(context)?;
|
|
383
|
+
if hash.kind_str() != "hash" && hash.kind_str() != "argument_list" {
|
|
384
|
+
return None;
|
|
385
|
+
}
|
|
386
|
+
// Only the last pair carries the closing bracket, and only when it is the one being omitted.
|
|
387
|
+
let pairs = super::nodes::children_in(hash, context);
|
|
388
|
+
if pairs.last().is_none_or(|last| last.id() != pair.id()) {
|
|
389
|
+
return None;
|
|
390
|
+
}
|
|
391
|
+
// A braced hash needs nothing: `{ bar: }` already reads as one.
|
|
392
|
+
if hash.kind_str() == "hash"
|
|
393
|
+
&& hash
|
|
394
|
+
.child(0)
|
|
395
|
+
.is_some_and(|first| context.source.node_text(first) == "{")
|
|
396
|
+
{
|
|
397
|
+
return None;
|
|
398
|
+
}
|
|
399
|
+
let call = find_ancestor_method_dispatch(context, hash)?;
|
|
400
|
+
let (selector, arguments) = (call.field("method")?, call.field("arguments")?);
|
|
401
|
+
// `dispatch_node.parenthesized?`
|
|
402
|
+
if arguments
|
|
403
|
+
.child(0)
|
|
404
|
+
.is_some_and(|first| context.source.node_text(first) == "(")
|
|
405
|
+
{
|
|
406
|
+
return None;
|
|
407
|
+
}
|
|
408
|
+
let first_argument = *super::nodes::children_in(arguments, context).first()?;
|
|
409
|
+
// The closing bracket only lands right when the omitted pair ends the argument list.
|
|
410
|
+
if arguments.end_byte() != pair.end_byte() {
|
|
411
|
+
return None;
|
|
412
|
+
}
|
|
413
|
+
// `return if last_expression?(dispatch) && !requires_parentheses_context?(dispatch)`: a call
|
|
414
|
+
// that ends its body needs no help, because `foo bar:` there still parses. One with anything
|
|
415
|
+
// written after it does -- `foo value:` would swallow the next line.
|
|
416
|
+
if last_expression(call, context) && !requires_parentheses_context(call, context) {
|
|
417
|
+
return None;
|
|
418
|
+
}
|
|
419
|
+
Some(Edit {
|
|
420
|
+
start: selector.end_byte(),
|
|
421
|
+
end: first_argument.start_byte(),
|
|
422
|
+
replacement: "(".to_owned(),
|
|
423
|
+
safe: true,
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/// `find_ancestor_method_dispatch_node`: the call, `super` or `yield` the hash is an argument of.
|
|
428
|
+
fn find_ancestor_method_dispatch<'tree>(
|
|
429
|
+
context: &'tree RuleContext<'_>,
|
|
430
|
+
hash: Node<'tree>,
|
|
431
|
+
) -> Option<Node<'tree>> {
|
|
432
|
+
let mut node = hash;
|
|
433
|
+
while let Some(parent) = node.parent_of(context) {
|
|
434
|
+
match parent.kind_str() {
|
|
435
|
+
"call" | "super" | "yield" => return Some(parent),
|
|
436
|
+
"argument_list" => node = parent,
|
|
437
|
+
_ => return None,
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
None
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// `requires_parentheses_context?`: the call sits where a bare `foo bar:` would be read as part of
|
|
444
|
+
/// the surrounding expression.
|
|
445
|
+
/// `last_expression?`: nothing is written after the call, once an enclosing assignment is followed
|
|
446
|
+
/// out to whatever holds *it*.
|
|
447
|
+
fn last_expression(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
448
|
+
if node.next_named_sibling().is_some() {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
let mut current = node.parent_of(context);
|
|
452
|
+
while let Some(ancestor) = current {
|
|
453
|
+
if matches!(ancestor.kind_str(), "assignment" | "operator_assignment") {
|
|
454
|
+
// `return last_expression?(assignment.parent) if assignment.parent&.assignment?`.
|
|
455
|
+
return match ancestor
|
|
456
|
+
.parent_of(context)
|
|
457
|
+
.filter(|parent| matches!(parent.kind_str(), "assignment" | "operator_assignment"))
|
|
458
|
+
{
|
|
459
|
+
Some(parent) => last_expression(parent, context),
|
|
460
|
+
None => ancestor.next_named_sibling().is_none(),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
current = ancestor.parent_of(context);
|
|
464
|
+
}
|
|
465
|
+
true
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
fn requires_parentheses_context(call: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
469
|
+
// `node.parent`: the grammar interposes an `argument_list` upstream has no node for, so the
|
|
470
|
+
// parent of a nested call is that list rather than the call it is an argument of.
|
|
471
|
+
let mut parent = call.parent_of(context);
|
|
472
|
+
while parent.is_some_and(|node| node.kind_str() == "argument_list") {
|
|
473
|
+
parent = parent.and_then(|node| node.parent_of(context));
|
|
474
|
+
}
|
|
475
|
+
parent.is_some_and(|parent| {
|
|
476
|
+
matches!(
|
|
477
|
+
parent.kind_str(),
|
|
478
|
+
"call" | "if" | "unless" | "super" | "until" | "while" | "yield"
|
|
479
|
+
)
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/// `node.value_omission?`.
|
|
484
|
+
fn omits_value(pair: Node<'_>) -> bool {
|
|
485
|
+
pair.field("value").is_none()
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/// `!pair_node.parent.hash_type?` inverted. A braceless hash has its pairs sitting in the argument
|
|
489
|
+
/// list, where upstream's parser still builds a `hash` around them; a hash *pattern* does not.
|
|
490
|
+
fn in_hash_literal(pair: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
491
|
+
// `Hash[foo: foo]` parks its pairs straight under the `element_reference`; upstream still
|
|
492
|
+
// builds a `hash` around them there, as it does for a braceless argument list.
|
|
493
|
+
pair.parent_of(context).is_some_and(|parent| {
|
|
494
|
+
matches!(
|
|
495
|
+
parent.kind_str(),
|
|
496
|
+
"hash" | "argument_list" | "element_reference"
|
|
497
|
+
)
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/// The pairs of each hash in the file, in source order.
|
|
502
|
+
fn hash_groups<'tree>(context: &'tree RuleContext<'_>) -> Vec<Vec<Node<'tree>>> {
|
|
503
|
+
let mut groups: Vec<(usize, Vec<Node<'tree>>)> = Vec::new();
|
|
504
|
+
for pair in context.nodes_of("pair") {
|
|
505
|
+
let Some(parent) = pair.parent_of(context) else {
|
|
506
|
+
continue;
|
|
507
|
+
};
|
|
508
|
+
if !matches!(
|
|
509
|
+
parent.kind_str(),
|
|
510
|
+
"hash" | "argument_list" | "element_reference"
|
|
511
|
+
) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
match groups.iter_mut().find(|(id, _)| *id == parent.id()) {
|
|
515
|
+
Some((_, list)) => list.push(pair),
|
|
516
|
+
None => groups.push((parent.id(), vec![pair])),
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
groups.into_iter().map(|(_, list)| list).collect()
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/// `require_hash_value?`: whether the value has to stay written out.
|
|
523
|
+
fn require_hash_value(context: &RuleContext<'_>, pair: Node<'_>) -> bool {
|
|
524
|
+
let Some(key) = pair.field("key") else {
|
|
525
|
+
return true;
|
|
526
|
+
};
|
|
527
|
+
if !is_symbol_key(key) || require_hash_value_around_hash_literal(context, pair) {
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
let Some(value) = pair.field("value") else {
|
|
531
|
+
return true;
|
|
532
|
+
};
|
|
533
|
+
// `hash_value.type?(:send, :lvar)`: only a bare name can be the value the key stands in for.
|
|
534
|
+
if !matches!(value.kind_str(), "identifier" | "call") {
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
let key_source = context.source.node_text(key);
|
|
538
|
+
// A key ending in `!` or `?` cannot be shortened: `{ foo?: }` is not a method call on the
|
|
539
|
+
// omitted side.
|
|
540
|
+
key_source != context.source.node_text(value)
|
|
541
|
+
|| key_source.ends_with('!')
|
|
542
|
+
|| key_source.ends_with('?')
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/// `node.key.sym_type?`. A key written before a colon is a symbol whatever it looks like.
|
|
546
|
+
fn is_symbol_key(key: Node<'_>) -> bool {
|
|
547
|
+
matches!(
|
|
548
|
+
key.kind_str(),
|
|
549
|
+
"hash_key_symbol" | "simple_symbol" | "delimited_symbol"
|
|
550
|
+
)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/// `require_hash_value_for_around_hash_literal?`: a braceless hash handed to a call written
|
|
554
|
+
/// without parentheses, inside a modifier form, keeps its values -- omitting one there changes
|
|
555
|
+
/// where the parser thinks the argument list ends.
|
|
556
|
+
fn require_hash_value_around_hash_literal(context: &RuleContext<'_>, pair: Node<'_>) -> bool {
|
|
557
|
+
let Some(parent) = pair.parent_of(context) else {
|
|
558
|
+
return false;
|
|
559
|
+
};
|
|
560
|
+
// `!node.parent.braces?`: a hash written with braces is delimited already.
|
|
561
|
+
if parent.kind_str() != "argument_list" {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
let Some(dispatch) = ancestor_method_dispatch(context, parent) else {
|
|
565
|
+
return false;
|
|
566
|
+
};
|
|
567
|
+
// `use_element_of_hash_literal_as_receiver?`: `{ value: }.do_something` is fine.
|
|
568
|
+
if dispatch.field("receiver") == Some(parent) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
if is_parenthesized_call(context, dispatch) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
// `use_modifier_form_without_parenthesized_method_call?`.
|
|
575
|
+
let mut current = dispatch;
|
|
576
|
+
while let Some(ancestor) = current.parent_of(context) {
|
|
577
|
+
if is_modifier_form(ancestor, context) {
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
current = ancestor;
|
|
581
|
+
}
|
|
582
|
+
false
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/// `find_ancestor_method_dispatch_node`: the call the hash is an argument of, unless that "call"
|
|
586
|
+
/// is an index read.
|
|
587
|
+
fn ancestor_method_dispatch<'tree>(
|
|
588
|
+
context: &'tree RuleContext<'_>,
|
|
589
|
+
list: Node<'tree>,
|
|
590
|
+
) -> Option<Node<'tree>> {
|
|
591
|
+
let ancestor = list.parent_of(context)?;
|
|
592
|
+
if !matches!(
|
|
593
|
+
ancestor.kind_str(),
|
|
594
|
+
"call" | "super" | "yield" | "method_call"
|
|
595
|
+
) {
|
|
596
|
+
return None;
|
|
597
|
+
}
|
|
598
|
+
// `brackets?`: `foo[bar: 1]` is not a dispatch the omission can confuse.
|
|
599
|
+
let method = ancestor
|
|
600
|
+
.field("method")
|
|
601
|
+
.map(|node| context.source.node_text(node));
|
|
602
|
+
if matches!(method, Some("[]" | "[]=")) {
|
|
603
|
+
return None;
|
|
604
|
+
}
|
|
605
|
+
Some(ancestor)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
fn is_parenthesized_call(context: &RuleContext<'_>, node: Node<'_>) -> bool {
|
|
609
|
+
node.field("arguments")
|
|
610
|
+
.is_some_and(|list| context.source.node_text(list).starts_with('('))
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/// `modifier_form?`: a trailing `if`, `unless`, `while`, `until` or `rescue`.
|
|
614
|
+
fn is_modifier_form(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
615
|
+
if !matches!(
|
|
616
|
+
node.kind_str(),
|
|
617
|
+
"if_modifier" | "unless_modifier" | "while_modifier" | "until_modifier" | "rescue_modifier"
|
|
618
|
+
) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
let _ = context;
|
|
622
|
+
true
|
|
623
|
+
}
|
|
624
|
+
|
|
81
625
|
/// `hash_node.parent&.return_type? && !hash_node.braces?`: the span of a braceless hash written
|
|
82
626
|
/// straight after `return`, given only for its first pair so the braces go on once.
|
|
83
627
|
///
|
|
@@ -91,7 +635,7 @@ fn returned_bare_hash(node: Node<'_>, context: &RuleContext<'_>) -> Option<(usiz
|
|
|
91
635
|
if context.parent(list)?.kind_str() != "return" {
|
|
92
636
|
return None;
|
|
93
637
|
}
|
|
94
|
-
let pairs: Vec<Node<'_>> = super::nodes::
|
|
638
|
+
let pairs: Vec<Node<'_>> = super::nodes::children_in(list, context)
|
|
95
639
|
.into_iter()
|
|
96
640
|
.filter(|child| child.kind_str() == "pair")
|
|
97
641
|
.collect();
|
|
@@ -111,7 +655,7 @@ fn argument_without_space(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
|
111
655
|
// The comparison is against **the hash**, not the argument list. A parenthesized call has its
|
|
112
656
|
// list start at the `(`, which always sits right after the selector, so comparing the list
|
|
113
657
|
// would put a space into every `func(:a => 0)`.
|
|
114
|
-
let first = super::nodes::
|
|
658
|
+
let first = super::nodes::children_in(list, context)
|
|
115
659
|
.into_iter()
|
|
116
660
|
.find(|child| child.kind_str() == "pair");
|
|
117
661
|
let Some(first) = first else {
|
|
@@ -140,44 +684,38 @@ fn key_name<'a>(node: Node<'_>, context: &'a RuleContext<'_>) -> &'a str {
|
|
|
140
684
|
text.strip_prefix(':').unwrap_or(text)
|
|
141
685
|
}
|
|
142
686
|
|
|
143
|
-
/// `sym_indices?`: whether every key of the hash this pair belongs to is a symbol the new syntax
|
|
144
|
-
/// can spell.
|
|
145
|
-
fn every_key_takes_the_new_syntax(
|
|
146
|
-
node: Node<'_>,
|
|
147
|
-
context: &RuleContext<'_>,
|
|
148
|
-
quoted_keys_allowed: bool,
|
|
149
|
-
) -> bool {
|
|
150
|
-
let Some(container) = node.parent_of(context) else {
|
|
151
|
-
return false;
|
|
152
|
-
};
|
|
153
|
-
let mut cursor = container.walk();
|
|
154
|
-
let pairs: Vec<Node<'_>> = container
|
|
155
|
-
.named_children(&mut cursor)
|
|
156
|
-
.filter(|child| child.kind_str() == "pair")
|
|
157
|
-
.collect();
|
|
158
|
-
!pairs.is_empty()
|
|
159
|
-
&& pairs
|
|
160
|
-
.iter()
|
|
161
|
-
.all(|pair| word_symbol_pair(*pair, context, quoted_keys_allowed))
|
|
162
|
-
}
|
|
163
|
-
|
|
164
687
|
/// `word_symbol_pair?`: a symbol key whose name the new syntax accepts.
|
|
165
688
|
///
|
|
166
689
|
/// A key written with a colon is already a symbol whatever it looks like -- `"a b": 1` reaches
|
|
167
690
|
/// RuboCop as a `dsym` -- so only the rocket form has to have its node kind checked.
|
|
168
|
-
fn word_symbol_pair(
|
|
691
|
+
fn word_symbol_pair(
|
|
692
|
+
node: Node<'_>,
|
|
693
|
+
context: &RuleContext<'_>,
|
|
694
|
+
quoted_keys_allowed: bool,
|
|
695
|
+
prefer_rockets_for_non_alnum: bool,
|
|
696
|
+
) -> bool {
|
|
169
697
|
let Some(key) = node.field("key") else {
|
|
170
698
|
return false;
|
|
171
699
|
};
|
|
172
700
|
let is_symbol = hash_rocket(node, context).is_none()
|
|
173
701
|
|| matches!(key.kind_str(), "simple_symbol" | "delimited_symbol");
|
|
174
|
-
is_symbol
|
|
702
|
+
is_symbol
|
|
703
|
+
&& acceptable_19_syntax_symbol(
|
|
704
|
+
context.source.node_text(key),
|
|
705
|
+
quoted_keys_allowed,
|
|
706
|
+
prefer_rockets_for_non_alnum,
|
|
707
|
+
)
|
|
175
708
|
}
|
|
176
709
|
|
|
177
|
-
fn acceptable_19_syntax_symbol(
|
|
710
|
+
fn acceptable_19_syntax_symbol(
|
|
711
|
+
text: &str,
|
|
712
|
+
quoted_keys_allowed: bool,
|
|
713
|
+
prefer_rockets_for_non_alnum: bool,
|
|
714
|
+
) -> bool {
|
|
178
715
|
let name = text.strip_prefix(':').unwrap_or(text);
|
|
179
716
|
if PLAIN_SYMBOL.is_match(name) {
|
|
180
|
-
return
|
|
717
|
+
return !(prefer_rockets_for_non_alnum
|
|
718
|
+
&& matches!(name.as_bytes().last(), Some(b'?' | b'!')));
|
|
181
719
|
}
|
|
182
720
|
quoted_keys_allowed
|
|
183
721
|
&& name.len() >= 2
|