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
data/build.rs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//! Records which commit the binary next to this build was made from.
|
|
2
|
+
//!
|
|
3
|
+
//! The fingerprint a measurement prints -- the binary's own hash -- answers "which binary", not
|
|
4
|
+
//! "whose fixes are in it". Those are different questions, and on 2026-08-17 the gap cost the team
|
|
5
|
+
//! five re-runs of a sixty-minute gate and two reports of bugs that had already been fixed on a
|
|
6
|
+
//! branch nobody's tree had merged. Each side was rigorous on its own: one printed the fingerprint,
|
|
7
|
+
//! the other reported how far its branch was ahead. Neither answers the question in between.
|
|
8
|
+
//!
|
|
9
|
+
//! Writing the commit beside the binary closes it without touching the CLI. `--version` stays
|
|
10
|
+
//! byte-identical to what RuboCop prints, which the drop-in replacement depends on, and nothing
|
|
11
|
+
//! reads this file at run time -- it exists for the measurement scripts, which can now say
|
|
12
|
+
//! `git merge-base --is-ancestor <fix> <sha>` instead of asking a person.
|
|
13
|
+
//!
|
|
14
|
+
//! The file lands next to the executable rather than inside `OUT_DIR` so that the scripts can find
|
|
15
|
+
//! it from the binary path alone; they are handed a path to `sonicop`, not a Cargo layout. A build
|
|
16
|
+
//! that cannot reach git writes `unknown` rather than failing: this is a convenience for measuring,
|
|
17
|
+
//! and a release built from a source tarball has no repository to ask.
|
|
18
|
+
|
|
19
|
+
use std::ffi::OsString;
|
|
20
|
+
use std::fs;
|
|
21
|
+
use std::io;
|
|
22
|
+
use std::path::{Path, PathBuf};
|
|
23
|
+
use std::process::Command;
|
|
24
|
+
|
|
25
|
+
fn main() {
|
|
26
|
+
// The commit marker changes only with git. The embedded cache fingerprint, however, must move
|
|
27
|
+
// with every input that can change diagnostics so a development build never accepts reports
|
|
28
|
+
// from different code merely because the package version stayed put.
|
|
29
|
+
println!("cargo:rerun-if-changed=.git/HEAD");
|
|
30
|
+
println!("cargo:rerun-if-changed=.git/refs");
|
|
31
|
+
for input in [
|
|
32
|
+
"Cargo.toml",
|
|
33
|
+
"Cargo.lock",
|
|
34
|
+
"build.rs",
|
|
35
|
+
"config/default.yml",
|
|
36
|
+
"src",
|
|
37
|
+
] {
|
|
38
|
+
println!("cargo:rerun-if-changed={input}");
|
|
39
|
+
}
|
|
40
|
+
let fingerprint = build_fingerprint().expect("failed to fingerprint Sonicop build inputs");
|
|
41
|
+
println!("cargo:rustc-env=SONICOP_BUILD_FINGERPRINT={fingerprint}");
|
|
42
|
+
|
|
43
|
+
let Some(profile_dir) = profile_dir() else {
|
|
44
|
+
return;
|
|
45
|
+
};
|
|
46
|
+
let sha = head_sha().unwrap_or_else(|| "unknown".to_owned());
|
|
47
|
+
let dirty = match working_tree_is_dirty() {
|
|
48
|
+
true => " dirty",
|
|
49
|
+
false => "",
|
|
50
|
+
};
|
|
51
|
+
let _ = std::fs::write(
|
|
52
|
+
profile_dir.join(".sonicop-build-sha"),
|
|
53
|
+
format!("{sha}{dirty}\n"),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Stable identity of everything that can affect the executable's lint result.
|
|
58
|
+
///
|
|
59
|
+
/// Hashing this once while compiling replaces reading and hashing the entire executable at every
|
|
60
|
+
/// startup. Besides source and embedded configuration, the lockfile covers dependency revisions;
|
|
61
|
+
/// compiler/target/cfg inputs cover builds whose source tree is byte-identical but semantics are
|
|
62
|
+
/// not. Test and documentation files are intentionally absent because they never enter the binary.
|
|
63
|
+
fn build_fingerprint() -> io::Result<blake3::Hash> {
|
|
64
|
+
let mut fingerprint = blake3::Hasher::new();
|
|
65
|
+
for input in [
|
|
66
|
+
"Cargo.toml",
|
|
67
|
+
"Cargo.lock",
|
|
68
|
+
"build.rs",
|
|
69
|
+
"config/default.yml",
|
|
70
|
+
"src",
|
|
71
|
+
] {
|
|
72
|
+
hash_path(Path::new(input), &mut fingerprint)?;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let mut environment = std::env::vars_os()
|
|
76
|
+
.filter(|(name, _)| {
|
|
77
|
+
let name = name.to_string_lossy();
|
|
78
|
+
matches!(
|
|
79
|
+
name.as_ref(),
|
|
80
|
+
"TARGET" | "PROFILE" | "OPT_LEVEL" | "DEBUG" | "CARGO_ENCODED_RUSTFLAGS"
|
|
81
|
+
) || name.starts_with("CARGO_CFG_")
|
|
82
|
+
|| name.starts_with("CARGO_FEATURE_")
|
|
83
|
+
})
|
|
84
|
+
.collect::<Vec<(OsString, OsString)>>();
|
|
85
|
+
environment.sort_unstable();
|
|
86
|
+
for (name, value) in environment {
|
|
87
|
+
hash_part(&mut fingerprint, name.as_encoded_bytes());
|
|
88
|
+
hash_part(&mut fingerprint, value.as_encoded_bytes());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
|
|
92
|
+
let version = Command::new(rustc).arg("-vV").output()?;
|
|
93
|
+
if !version.status.success() {
|
|
94
|
+
return Err(io::Error::other("rustc -vV failed"));
|
|
95
|
+
}
|
|
96
|
+
hash_part(&mut fingerprint, &version.stdout);
|
|
97
|
+
Ok(fingerprint.finalize())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn hash_path(path: &Path, fingerprint: &mut blake3::Hasher) -> io::Result<()> {
|
|
101
|
+
hash_part(fingerprint, path.as_os_str().as_encoded_bytes());
|
|
102
|
+
if path.is_dir() {
|
|
103
|
+
hash_part(fingerprint, b"directory");
|
|
104
|
+
let mut children = fs::read_dir(path)?
|
|
105
|
+
.collect::<Result<Vec<_>, _>>()?
|
|
106
|
+
.into_iter()
|
|
107
|
+
.map(|entry| entry.path())
|
|
108
|
+
.collect::<Vec<_>>();
|
|
109
|
+
children.sort_unstable();
|
|
110
|
+
for child in children {
|
|
111
|
+
hash_path(&child, fingerprint)?;
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
hash_part(fingerprint, b"file");
|
|
115
|
+
hash_part(fingerprint, &fs::read(path)?);
|
|
116
|
+
}
|
|
117
|
+
Ok(())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn hash_part(fingerprint: &mut blake3::Hasher, bytes: &[u8]) {
|
|
121
|
+
fingerprint.update(&(bytes.len() as u64).to_le_bytes());
|
|
122
|
+
fingerprint.update(bytes);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// `target/<profile>`, derived from where Cargo put this script's scratch space.
|
|
126
|
+
///
|
|
127
|
+
/// `OUT_DIR` is `target/<profile>/build/<pkg>-<hash>/out`, so the profile directory is three levels
|
|
128
|
+
/// up. Cargo has no environment variable that names it directly.
|
|
129
|
+
fn profile_dir() -> Option<PathBuf> {
|
|
130
|
+
let out_dir = PathBuf::from(std::env::var_os("OUT_DIR")?);
|
|
131
|
+
Some(out_dir.ancestors().nth(3)?.to_path_buf())
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn head_sha() -> Option<String> {
|
|
135
|
+
let done = Command::new("git")
|
|
136
|
+
.args(["rev-parse", "--short", "HEAD"])
|
|
137
|
+
.output()
|
|
138
|
+
.ok()?;
|
|
139
|
+
match done.status.success() {
|
|
140
|
+
true => Some(String::from_utf8(done.stdout).ok()?.trim().to_owned()),
|
|
141
|
+
false => None,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// Whether the tree the binary was built from carries uncommitted changes.
|
|
146
|
+
///
|
|
147
|
+
/// **A commit alone would overstate what the binary contains.** Half of the re-runs on 2026-08-17
|
|
148
|
+
/// were of trees whose commit was right and whose working copy had moved on, so the scripts need to
|
|
149
|
+
/// see the difference between "built from this commit" and "built from this commit plus edits".
|
|
150
|
+
fn working_tree_is_dirty() -> bool {
|
|
151
|
+
Command::new("git")
|
|
152
|
+
.args(["status", "--porcelain"])
|
|
153
|
+
.output()
|
|
154
|
+
.is_ok_and(|done| done.status.success() && !done.stdout.is_empty())
|
|
155
|
+
}
|
data/lib/sonicop/runner.rb
CHANGED
|
@@ -15,7 +15,13 @@ module Sonicop
|
|
|
15
15
|
MESSAGE
|
|
16
16
|
|
|
17
17
|
begin
|
|
18
|
-
exec(binary, *arguments)
|
|
18
|
+
# `[binary, binary]` = exec 形式の強制。`exec(binary, *arguments)` は arguments が空、
|
|
19
|
+
# つまり「カレントディレクトリを見る素の `sonicop`」という最も普通の呼び出しのとき
|
|
20
|
+
# `exec(String)` = commandline 形式に落ちる。この形式は空白で単語分割し、メタ文字が
|
|
21
|
+
# あればまるごと /bin/sh に渡すため、binary のパス次第で ENOENT や exit 127 になる。
|
|
22
|
+
# パスの出所は SONICOP_BINARY と gem の install 先で、どちらもメタ文字を含まない保証がない。
|
|
23
|
+
# argv0 は multi-arg 形式が既定で入れるものと同じ値を明示している。
|
|
24
|
+
exec([binary, binary], *arguments)
|
|
19
25
|
rescue SystemCallError => error
|
|
20
26
|
# 実行できない典型は「platform gem は入ったが実行環境と ABI が違う」ケース
|
|
21
27
|
# (musl 環境に glibc ビルドが入るなど)。素の errno だけでは原因が読めない。
|
data/lib/sonicop/version.rb
CHANGED
data/src/cli.rs
CHANGED
|
@@ -11,8 +11,9 @@ use crate::config::{Config, ConfigStore};
|
|
|
11
11
|
use crate::cop_name::{self, selector_matches};
|
|
12
12
|
use crate::diagnostic::{FileReport, Offense, Severity};
|
|
13
13
|
use crate::engine::{
|
|
14
|
-
CorrectMode, NO_SYNTAX_GUARD, Selection, correct_file,
|
|
15
|
-
|
|
14
|
+
CorrectMode, NO_SYNTAX_GUARD, ResultCache, Selection, correct_file,
|
|
15
|
+
discover_targets_with_store, inspect_files_with_store_cached, inspect_stdin, is_mandatory_cop,
|
|
16
|
+
offense_count, write_corrected,
|
|
16
17
|
};
|
|
17
18
|
use crate::formatter::{
|
|
18
19
|
Format, FormatOptions, offenses_by_cop, render, smart_path, yaml_single_quoted,
|
|
@@ -238,9 +239,11 @@ impl Cli {
|
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
pub fn run() -> i32 {
|
|
241
|
-
|
|
242
|
+
// Sized from the registry: a fixed table silently drops every cop past its end.
|
|
243
|
+
let names: Vec<&'static str> = crate::rules::rule_names().collect();
|
|
244
|
+
crate::profile::set_enabled(std::env::var_os("SONICOP_PROFILE").is_some(), names.len());
|
|
242
245
|
let code = run_inner();
|
|
243
|
-
crate::profile::report(&
|
|
246
|
+
crate::profile::report(&names);
|
|
244
247
|
code
|
|
245
248
|
}
|
|
246
249
|
|
|
@@ -304,10 +307,17 @@ fn output_paths_by_format(matches: &ArgMatches) -> Vec<Option<PathBuf>> {
|
|
|
304
307
|
|
|
305
308
|
let mut slots = vec![None; format_positions.len().max(1)];
|
|
306
309
|
for (out_position, path) in out_positions.into_iter().zip(out_paths) {
|
|
310
|
+
// `Options#parse`: `-o` attaches to the `-f` it follows. One written before any `-f` lands
|
|
311
|
+
// in `output_path` instead, and the only thing that reads it is `apply_default_formatter`'s
|
|
312
|
+
// `@options[:formatters] ||= [[format, output_path]]` -- a no-op once a `-f` was given. So
|
|
313
|
+
// with a `-f` present such a path is dropped, and the formatter writes to stdout; with none,
|
|
314
|
+
// it is where the default formatter writes.
|
|
307
315
|
let slot = format_positions
|
|
308
316
|
.iter()
|
|
309
|
-
.rposition(|format_position| *format_position < out_position)
|
|
310
|
-
|
|
317
|
+
.rposition(|format_position| *format_position < out_position);
|
|
318
|
+
let Some(slot) = slot.or_else(|| format_positions.is_empty().then_some(0)) else {
|
|
319
|
+
continue;
|
|
320
|
+
};
|
|
311
321
|
slots[slot].get_or_insert_with(|| path.clone());
|
|
312
322
|
}
|
|
313
323
|
slots
|
|
@@ -392,13 +402,28 @@ fn try_run(cli: Cli, outputs: &[Option<PathBuf>]) -> Result<i32> {
|
|
|
392
402
|
|
|
393
403
|
let correct_mode = cli.correct_mode();
|
|
394
404
|
let selection = build_selection(&cli, &config, correct_mode)?;
|
|
405
|
+
let result_cache = result_cache(&cli, &cwd, &selection, &config)?;
|
|
395
406
|
if !cli.list_target_files {
|
|
396
407
|
warn_unimplemented_enabled(&config, &selection);
|
|
397
408
|
}
|
|
398
409
|
|
|
399
410
|
let parallel = !cli.no_parallel && (cli.parallel || cli.stdin.is_none());
|
|
400
|
-
let
|
|
401
|
-
|
|
411
|
+
let inspection = inspect_inputs(
|
|
412
|
+
&cli,
|
|
413
|
+
&cwd,
|
|
414
|
+
&configs,
|
|
415
|
+
&selection,
|
|
416
|
+
parallel,
|
|
417
|
+
result_cache.as_ref(),
|
|
418
|
+
)?;
|
|
419
|
+
if let Some(cache) = &result_cache {
|
|
420
|
+
cache.prune();
|
|
421
|
+
}
|
|
422
|
+
let (mut reports, target_count) = match inspection {
|
|
423
|
+
Inspection::Reports {
|
|
424
|
+
reports,
|
|
425
|
+
target_count,
|
|
426
|
+
} => (reports, target_count),
|
|
402
427
|
Inspection::ListedTargets => return Ok(0),
|
|
403
428
|
};
|
|
404
429
|
|
|
@@ -426,22 +451,22 @@ fn try_run(cli: Cli, outputs: &[Option<PathBuf>]) -> Result<i32> {
|
|
|
426
451
|
// A file RuboCop could not finish counts as a failed run even when nothing else offended.
|
|
427
452
|
let failing = fail_level.failing(&reports) || had_errors;
|
|
428
453
|
|
|
454
|
+
filter_displayed_offenses(&mut reports, &cli, fail_level);
|
|
455
|
+
render_outputs(
|
|
456
|
+
&RenderRequest {
|
|
457
|
+
cli: &cli,
|
|
458
|
+
config: &config,
|
|
459
|
+
cwd: &cwd,
|
|
460
|
+
fail_level,
|
|
461
|
+
outputs,
|
|
462
|
+
corrected_count,
|
|
463
|
+
target_count,
|
|
464
|
+
elapsed: started.elapsed().as_secs_f64(),
|
|
465
|
+
},
|
|
466
|
+
&reports,
|
|
467
|
+
)?;
|
|
429
468
|
if let Some(corrected) = stdin_corrected {
|
|
430
|
-
|
|
431
|
-
} else {
|
|
432
|
-
filter_displayed_offenses(&mut reports, &cli, fail_level);
|
|
433
|
-
render_outputs(
|
|
434
|
-
&RenderRequest {
|
|
435
|
-
cli: &cli,
|
|
436
|
-
config: &config,
|
|
437
|
-
cwd: &cwd,
|
|
438
|
-
fail_level,
|
|
439
|
-
outputs,
|
|
440
|
-
corrected_count,
|
|
441
|
-
elapsed: started.elapsed().as_secs_f64(),
|
|
442
|
-
},
|
|
443
|
-
&reports,
|
|
444
|
-
)?;
|
|
469
|
+
print_corrected_stdin(&cli, &corrected)?;
|
|
445
470
|
}
|
|
446
471
|
|
|
447
472
|
Ok(i32::from(failing))
|
|
@@ -471,14 +496,69 @@ fn build_selection(cli: &Cli, config: &Config, correct_mode: CorrectMode) -> Res
|
|
|
471
496
|
ignore_disable_comments: cli.ignore_disable_comments,
|
|
472
497
|
display_suppressed: cli.display_suppressed,
|
|
473
498
|
correcting: correct_mode != CorrectMode::None,
|
|
499
|
+
// `LSP.enabled?`. `--lsp` sets it too, but that mode returns before anything is inspected,
|
|
500
|
+
// so `--editor-mode` is the only way it reaches a cop from here.
|
|
501
|
+
editor_mode: cli.editor_mode,
|
|
474
502
|
// **No flag turns this on from the command line.** The guard is switched off for tests
|
|
475
503
|
// only, and even then per case; a run reaches the environment variable or nothing.
|
|
476
504
|
skip_syntax_guard: false,
|
|
477
505
|
})
|
|
478
506
|
}
|
|
479
507
|
|
|
508
|
+
fn result_cache(
|
|
509
|
+
cli: &Cli,
|
|
510
|
+
cwd: &Path,
|
|
511
|
+
selection: &Selection,
|
|
512
|
+
config: &Config,
|
|
513
|
+
) -> Result<Option<ResultCache>> {
|
|
514
|
+
// Profiling reports what inspecting the files costs, so it serves cached reports to nobody --
|
|
515
|
+
// unless the run asks for the cache in as many words, which is how the cost of a cache *hit*
|
|
516
|
+
// gets measured at all.
|
|
517
|
+
let profiling = std::env::var_os("SONICOP_PROFILE").is_some();
|
|
518
|
+
let enabled = cli.cache.as_deref() != Some("false")
|
|
519
|
+
&& !selection.correcting
|
|
520
|
+
&& cli.stdin.is_none()
|
|
521
|
+
&& !cli.profile
|
|
522
|
+
&& !cli.memory
|
|
523
|
+
&& (!profiling || cli.cache.as_deref() == Some("true"));
|
|
524
|
+
if !enabled {
|
|
525
|
+
return Ok(None);
|
|
526
|
+
}
|
|
527
|
+
let root = cli
|
|
528
|
+
.cache_root
|
|
529
|
+
.clone()
|
|
530
|
+
.unwrap_or_else(|| default_cache_root(cwd));
|
|
531
|
+
let max_files = config.all_cops_value("MaxFilesInCache").unwrap_or(20_000);
|
|
532
|
+
if max_files == 0 {
|
|
533
|
+
return Ok(None);
|
|
534
|
+
}
|
|
535
|
+
ResultCache::new(root, config.path_base(), selection, max_files).map(Some)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
fn default_cache_root(cwd: &Path) -> PathBuf {
|
|
539
|
+
if let Some(root) = std::env::var_os("XDG_CACHE_HOME") {
|
|
540
|
+
return PathBuf::from(root).join("sonicop");
|
|
541
|
+
}
|
|
542
|
+
if cfg!(target_os = "macos")
|
|
543
|
+
&& let Some(home) = std::env::var_os("HOME")
|
|
544
|
+
{
|
|
545
|
+
return PathBuf::from(home).join("Library/Caches/sonicop");
|
|
546
|
+
}
|
|
547
|
+
if let Some(home) = std::env::var_os("HOME") {
|
|
548
|
+
return PathBuf::from(home).join(".cache/sonicop");
|
|
549
|
+
}
|
|
550
|
+
cwd.join(".sonicop-cache")
|
|
551
|
+
}
|
|
552
|
+
|
|
480
553
|
enum Inspection {
|
|
481
|
-
Reports
|
|
554
|
+
Reports {
|
|
555
|
+
reports: Vec<FileReport>,
|
|
556
|
+
/// What discovery produced, which `--fail-fast` leaves larger than the number of reports.
|
|
557
|
+
/// `JSONFormatter` takes `target_file_count` from `started(target_files)` and
|
|
558
|
+
/// `inspected_file_count` from `finished(inspected_files)`, so the two are separate counts
|
|
559
|
+
/// and only a run that inspects everything it found makes them equal.
|
|
560
|
+
target_count: usize,
|
|
561
|
+
},
|
|
482
562
|
ListedTargets,
|
|
483
563
|
}
|
|
484
564
|
|
|
@@ -488,6 +568,7 @@ fn inspect_inputs(
|
|
|
488
568
|
configs: &ConfigStore,
|
|
489
569
|
selection: &Selection,
|
|
490
570
|
parallel: bool,
|
|
571
|
+
cache: Option<&ResultCache>,
|
|
491
572
|
) -> Result<Inspection> {
|
|
492
573
|
let Some(stdin_path) = &cli.stdin else {
|
|
493
574
|
let mut targets = discover_targets_with_store(
|
|
@@ -503,6 +584,9 @@ fn inspect_inputs(
|
|
|
503
584
|
}
|
|
504
585
|
return Ok(Inspection::ListedTargets);
|
|
505
586
|
}
|
|
587
|
+
if let Some(cache) = cache {
|
|
588
|
+
cache.prepare(targets.len());
|
|
589
|
+
}
|
|
506
590
|
let reports = if cli.fail_fast {
|
|
507
591
|
targets.sort_by_key(|path| {
|
|
508
592
|
std::cmp::Reverse(
|
|
@@ -511,28 +595,40 @@ fn inspect_inputs(
|
|
|
511
595
|
.ok(),
|
|
512
596
|
)
|
|
513
597
|
});
|
|
514
|
-
inspect_fail_fast(&targets, configs, selection)?
|
|
598
|
+
inspect_fail_fast(&targets, configs, selection, cache)?
|
|
515
599
|
} else {
|
|
516
|
-
|
|
600
|
+
inspect_files_with_store_cached(&targets, configs, selection, parallel, cache)?
|
|
517
601
|
};
|
|
518
|
-
return Ok(Inspection::Reports
|
|
602
|
+
return Ok(Inspection::Reports {
|
|
603
|
+
reports,
|
|
604
|
+
target_count: targets.len(),
|
|
605
|
+
});
|
|
519
606
|
};
|
|
520
607
|
|
|
521
608
|
if !cli.paths.is_empty() {
|
|
522
609
|
bail!("--stdin requires exactly one path supplied as its argument and no file arguments");
|
|
523
610
|
}
|
|
524
|
-
|
|
611
|
+
// Read as bytes, not as text: `$stdin.binmode.read` (`options.rb:46`) is what RuboCop reads,
|
|
612
|
+
// and a source that is not valid UTF-8 is an offense to report rather than a run to abort.
|
|
613
|
+
let mut bytes = Vec::new();
|
|
525
614
|
io::stdin()
|
|
526
|
-
.
|
|
527
|
-
.context("failed to read
|
|
615
|
+
.read_to_end(&mut bytes)
|
|
616
|
+
.context("failed to read source from stdin")?;
|
|
528
617
|
let target_config = configs.for_path(stdin_path)?;
|
|
529
|
-
let report =
|
|
530
|
-
Ok(Inspection::Reports
|
|
618
|
+
let report = inspect_stdin(stdin_path.clone(), bytes, &target_config, selection)?;
|
|
619
|
+
Ok(Inspection::Reports {
|
|
620
|
+
reports: vec![report],
|
|
621
|
+
target_count: 1,
|
|
622
|
+
})
|
|
531
623
|
}
|
|
532
624
|
|
|
533
625
|
struct CorrectionRun {
|
|
534
626
|
reports: Vec<FileReport>,
|
|
535
627
|
corrected_count: usize,
|
|
628
|
+
/// The buffer `--stdin` is left holding, which is `@options[:stdin]` after the run: the
|
|
629
|
+
/// corrected source when a cop rewrote it, and the source that was read when none did.
|
|
630
|
+
/// `Team#autocorrect` (`cop/team.rb:175-178`) only assigns it on a rewrite, so the unchanged
|
|
631
|
+
/// case is not an absence -- it is the original buffer, and upstream prints that too.
|
|
536
632
|
stdin_corrected: Option<String>,
|
|
537
633
|
had_errors: bool,
|
|
538
634
|
}
|
|
@@ -566,12 +662,12 @@ fn apply_corrections(
|
|
|
566
662
|
eprintln!("{message}");
|
|
567
663
|
had_errors = true;
|
|
568
664
|
}
|
|
569
|
-
if
|
|
570
|
-
if
|
|
665
|
+
if cli.stdin.is_some() {
|
|
666
|
+
if correct_mode != CorrectMode::None {
|
|
571
667
|
stdin_corrected = Some(outcome.text);
|
|
572
|
-
} else {
|
|
573
|
-
write_corrected(&path, &outcome.text)?;
|
|
574
668
|
}
|
|
669
|
+
} else if outcome.rewritten {
|
|
670
|
+
write_corrected(&path, &outcome.text)?;
|
|
575
671
|
}
|
|
576
672
|
corrected_reports.push(outcome.report);
|
|
577
673
|
}
|
|
@@ -611,15 +707,26 @@ impl FailLevel {
|
|
|
611
707
|
}
|
|
612
708
|
}
|
|
613
709
|
|
|
710
|
+
/// `Runner#considered_failure?` (`runner.rb:561-569`).
|
|
711
|
+
///
|
|
712
|
+
/// `autocorrect` adds a reason to fail; it does not replace the one every run has. Upstream
|
|
713
|
+
/// returns early for a correctable offense **and then falls through** to the severity
|
|
714
|
+
/// comparison, so an uncorrectable offense at or above the threshold still fails the run.
|
|
715
|
+
/// Reading the pseudo level as "correctable offenses only" instead lets `--fail-level
|
|
716
|
+
/// autocorrect` exit 0 on a `Metrics/MethodLength` no cop can fix -- a green CI on code
|
|
717
|
+
/// RuboCop fails, which is the one direction a linter must never be wrong in.
|
|
614
718
|
fn failing(self, reports: &[FileReport]) -> bool {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
719
|
+
// `minimum_severity_to_fail` reads `autocorrect` as `:refactor`, which [`Self::severity`]
|
|
720
|
+
// already does, so the threshold applies whichever level was asked for.
|
|
721
|
+
if offense_count(reports, self.severity()) > 0 {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
// A correctable offense fails even once it has been corrected.
|
|
725
|
+
self == Self::Autocorrect
|
|
726
|
+
&& reports
|
|
618
727
|
.iter()
|
|
619
728
|
.flat_map(|report| &report.offenses)
|
|
620
|
-
.any(Offense::is_correctable)
|
|
621
|
-
Self::Severity(severity) => offense_count(reports, severity) > 0,
|
|
622
|
-
}
|
|
729
|
+
.any(Offense::is_correctable)
|
|
623
730
|
}
|
|
624
731
|
}
|
|
625
732
|
|
|
@@ -743,11 +850,17 @@ fn inspect_fail_fast(
|
|
|
743
850
|
paths: &[PathBuf],
|
|
744
851
|
configs: &ConfigStore,
|
|
745
852
|
selection: &Selection,
|
|
853
|
+
cache: Option<&ResultCache>,
|
|
746
854
|
) -> Result<Vec<FileReport>> {
|
|
747
855
|
let mut reports = Vec::new();
|
|
748
856
|
for path in paths {
|
|
749
|
-
let mut inspected =
|
|
750
|
-
|
|
857
|
+
let mut inspected = inspect_files_with_store_cached(
|
|
858
|
+
std::slice::from_ref(path),
|
|
859
|
+
configs,
|
|
860
|
+
selection,
|
|
861
|
+
false,
|
|
862
|
+
cache,
|
|
863
|
+
)?;
|
|
751
864
|
let has_offense = inspected
|
|
752
865
|
.first()
|
|
753
866
|
.is_some_and(|report| !report.offenses.is_empty());
|
|
@@ -944,7 +1057,24 @@ fn generate_todo(reports: &[FileReport], cwd: &Path, cli: &Cli) -> Result<()> {
|
|
|
944
1057
|
}
|
|
945
1058
|
fs::write(cwd.join(".rubocop_todo.yml"), output)?;
|
|
946
1059
|
let root_config = cwd.join(".rubocop.yml");
|
|
947
|
-
|
|
1060
|
+
// `add_inheritance_from_auto_generated_file` reads the file only after `File.exist?` says
|
|
1061
|
+
// there is one, so "absent" and "unreadable" are separate answers and only the first means
|
|
1062
|
+
// "write a new one". Collapsing them -- `unwrap_or_default()` -- turns every read failure into
|
|
1063
|
+
// an empty string, and the prepend below silently becomes a replace: a `.rubocop.yml` that is
|
|
1064
|
+
// not valid UTF-8, or that the process cannot open, is overwritten by the one line this would
|
|
1065
|
+
// have added to it, with a zero exit code and nothing on stderr. Fail instead.
|
|
1066
|
+
let existing = match fs::read_to_string(&root_config) {
|
|
1067
|
+
Ok(existing) => existing,
|
|
1068
|
+
Err(error) if error.kind() == io::ErrorKind::NotFound => String::new(),
|
|
1069
|
+
Err(error) => {
|
|
1070
|
+
return Err(error).with_context(|| {
|
|
1071
|
+
format!(
|
|
1072
|
+
"failed to read {}; refusing to replace it with the generated inheritance",
|
|
1073
|
+
root_config.display()
|
|
1074
|
+
)
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
948
1078
|
if !existing.contains(".rubocop_todo.yml") {
|
|
949
1079
|
let addition = if existing.is_empty() {
|
|
950
1080
|
"inherit_from: .rubocop_todo.yml\n".to_owned()
|
|
@@ -979,6 +1109,9 @@ struct RenderRequest<'a> {
|
|
|
979
1109
|
/// Destination per format, already paired the way RuboCop pairs `--out` with `--format`.
|
|
980
1110
|
outputs: &'a [Option<PathBuf>],
|
|
981
1111
|
corrected_count: usize,
|
|
1112
|
+
/// How many files discovery produced, which is not how many were inspected once `--fail-fast`
|
|
1113
|
+
/// stops the run early. See [`Inspection::Reports`].
|
|
1114
|
+
target_count: usize,
|
|
982
1115
|
elapsed: f64,
|
|
983
1116
|
}
|
|
984
1117
|
|
|
@@ -990,6 +1123,7 @@ fn render_outputs(request: &RenderRequest<'_>, reports: &[FileReport]) -> Result
|
|
|
990
1123
|
fail_level,
|
|
991
1124
|
outputs,
|
|
992
1125
|
corrected_count,
|
|
1126
|
+
target_count,
|
|
993
1127
|
elapsed,
|
|
994
1128
|
} = *request;
|
|
995
1129
|
let formats = if cli.formats.is_empty() {
|
|
@@ -1021,6 +1155,7 @@ fn render_outputs(request: &RenderRequest<'_>, reports: &[FileReport]) -> Result
|
|
|
1021
1155
|
extra_details: cli.extra_details,
|
|
1022
1156
|
color,
|
|
1023
1157
|
corrected_count,
|
|
1158
|
+
target_file_count: target_count,
|
|
1024
1159
|
fail_level: fail_level.severity(),
|
|
1025
1160
|
safe_autocorrect: cli.correct_mode() == CorrectMode::Safe,
|
|
1026
1161
|
display_only_failed: cli.display_only_failed,
|
|
@@ -1038,6 +1173,42 @@ fn render_outputs(request: &RenderRequest<'_>, reports: &[FileReport]) -> Result
|
|
|
1038
1173
|
Ok(())
|
|
1039
1174
|
}
|
|
1040
1175
|
|
|
1176
|
+
/// `ExecuteRunner::INTEGRATION_FORMATTERS`: the formats whose consumers parse the whole stream, so
|
|
1177
|
+
/// nothing may be appended to them. Both the short and the long spelling of each.
|
|
1178
|
+
const INTEGRATION_FORMATTERS: [&str; 6] = ["h", "html", "j", "json", "ju", "junit"];
|
|
1179
|
+
|
|
1180
|
+
/// Appends the source `--stdin` was left holding to a formatter's report, the way
|
|
1181
|
+
/// `ExecuteRunner#maybe_print_corrected_source` (`cli/command/execute_runner.rb:92-102`) does.
|
|
1182
|
+
///
|
|
1183
|
+
/// An editor driving `--stdin -a` reads the corrected buffer off the end of the output, which is
|
|
1184
|
+
/// what the twenty `=` mark the start of. The report still comes first: printing only the buffer
|
|
1185
|
+
/// leaves nothing to say what was corrected, and with `--format json` it puts raw Ruby where a
|
|
1186
|
+
/// caller asked for JSON and will try to parse it.
|
|
1187
|
+
///
|
|
1188
|
+
/// Two details are upstream's rather than chosen here. The check is against `@options[:format]`,
|
|
1189
|
+
/// which the option parser assigns per `-f`, so it is the **last** format on the command line that
|
|
1190
|
+
/// decides -- `-f json -f simple` appends. And `--stderr` moves the separator but not the source:
|
|
1191
|
+
/// upstream picks the stream for the `puts` and then `print`s to `$stdout` unconditionally, by
|
|
1192
|
+
/// which point the redirect around the runner has already been undone.
|
|
1193
|
+
fn print_corrected_stdin(cli: &Cli, corrected: &str) -> Result<()> {
|
|
1194
|
+
if cli
|
|
1195
|
+
.formats
|
|
1196
|
+
.last()
|
|
1197
|
+
.is_some_and(|format| INTEGRATION_FORMATTERS.contains(&format.as_str()))
|
|
1198
|
+
{
|
|
1199
|
+
return Ok(());
|
|
1200
|
+
}
|
|
1201
|
+
let separator = format!("{}\n", "=".repeat(20));
|
|
1202
|
+
if cli.stderr {
|
|
1203
|
+
io::stderr().write_all(separator.as_bytes())?;
|
|
1204
|
+
} else {
|
|
1205
|
+
io::stdout().write_all(separator.as_bytes())?;
|
|
1206
|
+
}
|
|
1207
|
+
io::stdout().write_all(corrected.as_bytes())?;
|
|
1208
|
+
io::stdout().flush()?;
|
|
1209
|
+
Ok(())
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1041
1212
|
fn init_config(cwd: &Path) -> Result<i32> {
|
|
1042
1213
|
let path = cwd.join(".rubocop.yml");
|
|
1043
1214
|
if path.exists() {
|