rubocop 1.79.2 → 1.87.0
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/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/config/default.yml +185 -20
- data/config/obsoletion.yml +9 -0
- data/exe/rubocop +1 -8
- data/lib/rubocop/cache_config.rb +29 -0
- data/lib/rubocop/cli/command/auto_generate_config.rb +30 -4
- data/lib/rubocop/cli/command/list_enabled_cops_for.rb +40 -0
- data/lib/rubocop/cli/command/lsp.rb +1 -1
- data/lib/rubocop/cli/command/mcp.rb +19 -0
- data/lib/rubocop/cli/command/show_cops.rb +2 -2
- data/lib/rubocop/cli/command/show_docs_url.rb +4 -8
- data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
- data/lib/rubocop/cli.rb +35 -9
- data/lib/rubocop/comment_config.rb +59 -17
- data/lib/rubocop/config.rb +14 -10
- data/lib/rubocop/config_finder.rb +1 -1
- data/lib/rubocop/config_loader.rb +37 -23
- data/lib/rubocop/config_loader_resolver.rb +20 -10
- data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -2
- data/lib/rubocop/config_store.rb +7 -2
- data/lib/rubocop/config_validator.rb +1 -1
- data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
- data/lib/rubocop/cop/base.rb +8 -2
- data/lib/rubocop/cop/bundler/gem_version.rb +28 -28
- data/lib/rubocop/cop/bundler/ordered_gems.rb +1 -2
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +26 -7
- data/lib/rubocop/cop/correctors/condition_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
- data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +1 -5
- data/lib/rubocop/cop/correctors/parentheses_corrector.rb +33 -2
- data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
- data/lib/rubocop/cop/correctors.rb +28 -0
- data/lib/rubocop/cop/documentation.rb +2 -3
- data/lib/rubocop/cop/exclude_limit.rb +31 -5
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -2
- data/lib/rubocop/cop/gemspec/require_mfa.rb +5 -5
- data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +12 -7
- data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +8 -8
- data/lib/rubocop/cop/internal_affairs/itblock_handler.rb +69 -0
- data/lib/rubocop/cop/internal_affairs/location_exists.rb +28 -2
- data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +1 -0
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +9 -9
- data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_processor.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
- data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/useless_message_assertion.rb +4 -4
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/argument_alignment.rb +2 -2
- data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/begin_end_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/case_indentation.rb +3 -1
- data/lib/rubocop/cop/layout/class_structure.rb +14 -7
- data/lib/rubocop/cop/layout/dot_position.rb +2 -2
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +26 -7
- data/lib/rubocop/cop/layout/empty_line_between_defs.rb +31 -13
- data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +2 -2
- data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
- data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +12 -2
- data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +16 -2
- data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +16 -2
- data/lib/rubocop/cop/layout/end_alignment.rb +10 -3
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +34 -1
- data/lib/rubocop/cop/layout/first_array_element_line_break.rb +26 -0
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +7 -1
- data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +25 -25
- data/lib/rubocop/cop/layout/hash_alignment.rb +3 -6
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
- data/lib/rubocop/cop/layout/heredoc_indentation.rb +33 -3
- data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +123 -7
- data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
- data/lib/rubocop/cop/layout/line_length.rb +26 -9
- data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +57 -57
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +9 -2
- data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -0
- data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +56 -56
- data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +229 -39
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
- data/lib/rubocop/cop/layout/parameter_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -1
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +13 -3
- data/lib/rubocop/cop/layout/space_after_comma.rb +2 -10
- data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_keyword.rb +4 -2
- data/lib/rubocop/cop/layout/space_before_brackets.rb +1 -1
- data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +9 -8
- data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
- data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
- data/lib/rubocop/cop/lint/circular_argument_reference.rb +47 -3
- data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +4 -3
- data/lib/rubocop/cop/lint/constant_reassignment.rb +93 -11
- data/lib/rubocop/cop/lint/constant_resolution.rb +6 -6
- data/lib/rubocop/cop/lint/cop_directive_syntax.rb +14 -8
- data/lib/rubocop/cop/lint/data_define_override.rb +63 -0
- data/lib/rubocop/cop/lint/debugger.rb +0 -2
- data/lib/rubocop/cop/lint/deprecated_constants.rb +1 -1
- data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
- data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +4 -4
- data/lib/rubocop/cop/lint/duplicate_methods.rb +111 -12
- data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
- data/lib/rubocop/cop/lint/else_layout.rb +19 -0
- data/lib/rubocop/cop/lint/empty_block.rb +1 -1
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +6 -1
- data/lib/rubocop/cop/lint/empty_in_pattern.rb +8 -1
- data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
- data/lib/rubocop/cop/lint/empty_when.rb +8 -1
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +1 -1
- data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
- data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -1
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -9
- data/lib/rubocop/cop/lint/multiple_comparison.rb +2 -2
- data/lib/rubocop/cop/lint/next_without_accumulator.rb +2 -0
- data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +4 -0
- data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +4 -2
- data/lib/rubocop/cop/lint/number_conversion.rb +6 -6
- data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -1
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -13
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +23 -9
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +2 -11
- data/lib/rubocop/cop/lint/redundant_require_statement.rb +4 -2
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +23 -6
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +8 -2
- data/lib/rubocop/cop/lint/redundant_type_conversion.rb +3 -3
- data/lib/rubocop/cop/lint/require_relative_self_path.rb +3 -1
- data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +17 -0
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +7 -1
- data/lib/rubocop/cop/lint/self_assignment.rb +15 -6
- data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
- data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
- data/lib/rubocop/cop/lint/struct_new_override.rb +17 -1
- data/lib/rubocop/cop/lint/syntax.rb +25 -1
- data/lib/rubocop/cop/lint/to_json.rb +12 -16
- data/lib/rubocop/cop/lint/trailing_comma_in_attribute_declaration.rb +1 -0
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -1
- data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -0
- data/lib/rubocop/cop/lint/unreachable_code.rb +7 -5
- data/lib/rubocop/cop/lint/unreachable_pattern_branch.rb +113 -0
- data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
- data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
- data/lib/rubocop/cop/lint/useless_assignment.rb +48 -25
- data/lib/rubocop/cop/lint/useless_constant_scoping.rb +4 -4
- data/lib/rubocop/cop/lint/useless_default_value_argument.rb +2 -0
- data/lib/rubocop/cop/lint/useless_or.rb +15 -2
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +1 -1
- data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +37 -11
- data/lib/rubocop/cop/lint/void.rb +39 -12
- data/lib/rubocop/cop/message_annotator.rb +1 -1
- data/lib/rubocop/cop/metrics/block_length.rb +1 -1
- data/lib/rubocop/cop/metrics/block_nesting.rb +23 -0
- data/lib/rubocop/cop/metrics/method_length.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +4 -3
- data/lib/rubocop/cop/metrics/utils/iterating_block.rb +1 -1
- data/lib/rubocop/cop/migration/department_name.rb +12 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +2 -2
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +4 -6
- data/lib/rubocop/cop/mixin/code_length.rb +1 -1
- data/lib/rubocop/cop/mixin/configurable_max.rb +6 -5
- data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +5 -5
- data/lib/rubocop/cop/mixin/hash_transform_method/autocorrection.rb +63 -0
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +10 -60
- data/lib/rubocop/cop/mixin/line_length_help.rb +21 -2
- data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
- data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +1 -1
- data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +1 -1
- data/lib/rubocop/cop/mixin/project_index_help.rb +48 -0
- data/lib/rubocop/cop/mixin/space_after_punctuation.rb +5 -4
- data/lib/rubocop/cop/mixin/statement_modifier.rb +0 -6
- data/lib/rubocop/cop/mixin/trailing_comma.rb +8 -5
- data/lib/rubocop/cop/mixin.rb +86 -0
- data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -1
- data/lib/rubocop/cop/naming/block_parameter_name.rb +1 -1
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -1
- data/lib/rubocop/cop/naming/method_name.rb +5 -3
- data/lib/rubocop/cop/naming/predicate_method.rb +32 -8
- data/lib/rubocop/cop/naming/predicate_prefix.rb +12 -12
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
- data/lib/rubocop/cop/offense.rb +17 -1
- data/lib/rubocop/cop/registry.rb +62 -38
- data/lib/rubocop/cop/security/eval.rb +15 -2
- data/lib/rubocop/cop/security/io_methods.rb +1 -1
- data/lib/rubocop/cop/security/json_load.rb +33 -11
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +15 -4
- data/lib/rubocop/cop/style/accessor_grouping.rb +4 -2
- data/lib/rubocop/cop/style/alias.rb +14 -2
- data/lib/rubocop/cop/style/and_or.rb +1 -0
- data/lib/rubocop/cop/style/arguments_forwarding.rb +25 -7
- data/lib/rubocop/cop/style/array_intersect.rb +46 -12
- data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
- data/lib/rubocop/cop/style/array_join.rb +4 -2
- data/lib/rubocop/cop/style/ascii_comments.rb +6 -3
- data/lib/rubocop/cop/style/attr.rb +5 -2
- data/lib/rubocop/cop/style/bare_percent_literals.rb +4 -3
- data/lib/rubocop/cop/style/begin_block.rb +3 -1
- data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
- data/lib/rubocop/cop/style/block_delimiters.rb +27 -34
- data/lib/rubocop/cop/style/case_equality.rb +15 -13
- data/lib/rubocop/cop/style/character_literal.rb +2 -2
- data/lib/rubocop/cop/style/class_and_module_children.rb +19 -2
- data/lib/rubocop/cop/style/collection_compact.rb +36 -16
- data/lib/rubocop/cop/style/colon_method_call.rb +3 -1
- data/lib/rubocop/cop/style/concat_array_literals.rb +2 -0
- data/lib/rubocop/cop/style/conditional_assignment.rb +8 -18
- data/lib/rubocop/cop/style/constant_visibility.rb +17 -12
- data/lib/rubocop/cop/style/copyright.rb +22 -11
- data/lib/rubocop/cop/style/date_time.rb +2 -2
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +1 -1
- data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +6 -1
- data/lib/rubocop/cop/style/documentation.rb +6 -6
- data/lib/rubocop/cop/style/documentation_method.rb +8 -8
- data/lib/rubocop/cop/style/double_negation.rb +1 -1
- data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
- data/lib/rubocop/cop/style/each_with_object.rb +2 -0
- data/lib/rubocop/cop/style/empty_block_parameter.rb +1 -1
- data/lib/rubocop/cop/style/empty_class_definition.rb +119 -0
- data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -1
- data/lib/rubocop/cop/style/empty_method.rb +0 -6
- data/lib/rubocop/cop/style/encoding.rb +7 -1
- data/lib/rubocop/cop/style/end_block.rb +3 -1
- data/lib/rubocop/cop/style/endless_method.rb +23 -5
- data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
- data/lib/rubocop/cop/style/file_open.rb +84 -0
- data/lib/rubocop/cop/style/file_write.rb +18 -16
- data/lib/rubocop/cop/style/float_division.rb +15 -1
- data/lib/rubocop/cop/style/for.rb +3 -0
- data/lib/rubocop/cop/style/format_string.rb +4 -3
- data/lib/rubocop/cop/style/format_string_token.rb +49 -5
- data/lib/rubocop/cop/style/global_vars.rb +5 -2
- data/lib/rubocop/cop/style/guard_clause.rb +27 -22
- data/lib/rubocop/cop/style/hash_as_last_array_item.rb +27 -9
- data/lib/rubocop/cop/style/hash_conversion.rb +1 -1
- data/lib/rubocop/cop/style/hash_lookup_method.rb +106 -0
- data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
- data/lib/rubocop/cop/style/hash_transform_keys.rb +17 -7
- data/lib/rubocop/cop/style/hash_transform_values.rb +17 -7
- data/lib/rubocop/cop/style/if_inside_else.rb +16 -7
- data/lib/rubocop/cop/style/if_unless_modifier.rb +57 -17
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +12 -12
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +4 -1
- data/lib/rubocop/cop/style/if_with_semicolon.rb +7 -5
- data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
- data/lib/rubocop/cop/style/inline_comment.rb +4 -1
- data/lib/rubocop/cop/style/ip_addresses.rb +1 -2
- data/lib/rubocop/cop/style/lambda_call.rb +8 -8
- data/lib/rubocop/cop/style/magic_comment_format.rb +3 -3
- data/lib/rubocop/cop/style/map_join.rb +123 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +15 -2
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +17 -4
- data/lib/rubocop/cop/style/method_def_parentheses.rb +2 -4
- data/lib/rubocop/cop/style/min_max_comparison.rb +1 -1
- data/lib/rubocop/cop/style/module_member_existence_check.rb +110 -0
- data/lib/rubocop/cop/style/multiline_if_then.rb +4 -4
- data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -4
- data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
- data/lib/rubocop/cop/style/negative_array_index.rb +220 -0
- data/lib/rubocop/cop/style/nil_comparison.rb +11 -10
- data/lib/rubocop/cop/style/nil_lambda.rb +1 -1
- data/lib/rubocop/cop/style/non_nil_check.rb +5 -11
- data/lib/rubocop/cop/style/not.rb +2 -0
- data/lib/rubocop/cop/style/numeric_literals.rb +3 -2
- data/lib/rubocop/cop/style/one_class_per_file.rb +115 -0
- data/lib/rubocop/cop/style/one_line_conditional.rb +21 -12
- data/lib/rubocop/cop/style/operator_method_call.rb +11 -2
- data/lib/rubocop/cop/style/parallel_assignment.rb +6 -2
- data/lib/rubocop/cop/style/partition_instead_of_double_select.rb +270 -0
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -0
- data/lib/rubocop/cop/style/predicate_with_kind.rb +84 -0
- data/lib/rubocop/cop/style/preferred_hash_methods.rb +12 -12
- data/lib/rubocop/cop/style/proc.rb +3 -2
- data/lib/rubocop/cop/style/raise_args.rb +1 -1
- data/lib/rubocop/cop/style/reduce_to_hash.rb +200 -0
- data/lib/rubocop/cop/style/redundant_argument.rb +2 -0
- data/lib/rubocop/cop/style/redundant_array_constructor.rb +2 -2
- data/lib/rubocop/cop/style/redundant_begin.rb +37 -3
- data/lib/rubocop/cop/style/redundant_condition.rb +6 -3
- data/lib/rubocop/cop/style/redundant_constant_base.rb +5 -5
- data/lib/rubocop/cop/style/redundant_each.rb +3 -3
- data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +1 -1
- data/lib/rubocop/cop/style/redundant_format.rb +26 -5
- data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
- data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +26 -10
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +16 -0
- data/lib/rubocop/cop/style/redundant_min_max_by.rb +93 -0
- data/lib/rubocop/cop/style/redundant_parentheses.rb +36 -30
- data/lib/rubocop/cop/style/redundant_percent_q.rb +5 -3
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +9 -0
- data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +2 -2
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
- data/lib/rubocop/cop/style/redundant_return.rb +3 -1
- data/lib/rubocop/cop/style/redundant_self.rb +2 -2
- data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +0 -5
- data/lib/rubocop/cop/style/redundant_sort.rb +7 -7
- data/lib/rubocop/cop/style/redundant_struct_keyword_init.rb +114 -0
- data/lib/rubocop/cop/style/regexp_literal.rb +31 -2
- data/lib/rubocop/cop/style/rescue_modifier.rb +3 -3
- data/lib/rubocop/cop/style/reverse_find.rb +51 -0
- data/lib/rubocop/cop/style/safe_navigation.rb +25 -8
- data/lib/rubocop/cop/style/select_by_kind.rb +158 -0
- data/lib/rubocop/cop/style/select_by_range.rb +197 -0
- data/lib/rubocop/cop/style/select_by_regexp.rb +51 -21
- data/lib/rubocop/cop/style/self_assignment.rb +1 -1
- data/lib/rubocop/cop/style/semicolon.rb +25 -7
- data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -1
- data/lib/rubocop/cop/style/single_line_methods.rb +3 -1
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +12 -3
- data/lib/rubocop/cop/style/special_global_vars.rb +6 -1
- data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
- data/lib/rubocop/cop/style/struct_inheritance.rb +13 -0
- data/lib/rubocop/cop/style/super_arguments.rb +2 -2
- data/lib/rubocop/cop/style/symbol_array.rb +1 -1
- data/lib/rubocop/cop/style/symbol_proc.rb +7 -6
- data/lib/rubocop/cop/style/tally_method.rb +181 -0
- data/lib/rubocop/cop/style/top_level_method_definition.rb +2 -2
- data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
- data/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +1 -1
- data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +11 -11
- data/lib/rubocop/cop/style/unless_else.rb +10 -9
- data/lib/rubocop/cop/style/unless_logical_operators.rb +3 -3
- data/lib/rubocop/cop/style/while_until_modifier.rb +16 -0
- data/lib/rubocop/cop/style/yoda_condition.rb +1 -1
- data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
- data/lib/rubocop/cop/team.rb +87 -36
- data/lib/rubocop/cop/util.rb +2 -3
- data/lib/rubocop/cop/utils/format_string.rb +10 -0
- data/lib/rubocop/cop/variable_force/branch.rb +30 -6
- data/lib/rubocop/cop/variable_force/variable.rb +1 -1
- data/lib/rubocop/cop/variable_force.rb +9 -7
- data/lib/rubocop/cops_documentation_generator.rb +4 -4
- data/lib/rubocop/directive_comment.rb +48 -4
- data/lib/rubocop/file_patterns.rb +9 -1
- data/lib/rubocop/formatter/clang_style_formatter.rb +5 -2
- data/lib/rubocop/formatter/disabled_config_formatter.rb +24 -7
- data/lib/rubocop/formatter/formatter_set.rb +2 -2
- data/lib/rubocop/formatter/junit_formatter.rb +1 -1
- data/lib/rubocop/formatter/simple_text_formatter.rb +0 -2
- data/lib/rubocop/formatter/tap_formatter.rb +5 -2
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
- data/lib/rubocop/formatter.rb +22 -21
- data/lib/rubocop/lsp/diagnostic.rb +18 -33
- data/lib/rubocop/lsp/disable_comment_edits.rb +135 -0
- data/lib/rubocop/lsp/routes.rb +43 -7
- data/lib/rubocop/lsp/runtime.rb +13 -4
- data/lib/rubocop/lsp/stdin_runner.rb +8 -17
- data/lib/rubocop/magic_comment.rb +20 -0
- data/lib/rubocop/mcp/server.rb +200 -0
- data/lib/rubocop/options.rb +35 -4
- data/lib/rubocop/path_util.rb +14 -2
- data/lib/rubocop/plugin/loader.rb +1 -1
- data/lib/rubocop/project_index_loader.rb +66 -0
- data/lib/rubocop/rake_task.rb +1 -1
- data/lib/rubocop/remote_config.rb +10 -8
- data/lib/rubocop/result_cache.rb +61 -38
- data/lib/rubocop/rspec/cop_helper.rb +8 -0
- data/lib/rubocop/rspec/shared_contexts.rb +39 -5
- data/lib/rubocop/rspec/support.rb +2 -1
- data/lib/rubocop/runner.rb +134 -57
- data/lib/rubocop/server/cache.rb +6 -29
- data/lib/rubocop/server/core.rb +2 -0
- data/lib/rubocop/target_finder.rb +17 -10
- data/lib/rubocop/target_ruby.rb +31 -14
- data/lib/rubocop/version.rb +21 -3
- data/lib/rubocop.rb +28 -96
- data/lib/ruby_lsp/rubocop/addon.rb +23 -8
- data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
- metadata +38 -9
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module LSP
|
|
5
|
+
# Builds LSP text edits for rubocop:disable comments.
|
|
6
|
+
# @api private
|
|
7
|
+
class DisableCommentEdits
|
|
8
|
+
def initialize(offense:, document_encoding:, processed_source:)
|
|
9
|
+
@offense = offense
|
|
10
|
+
@document_encoding = document_encoding
|
|
11
|
+
@processed_source = processed_source
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def edits
|
|
15
|
+
literal_range = multiline_literal_range
|
|
16
|
+
return block_disable_comments(literal_range) if literal_range
|
|
17
|
+
|
|
18
|
+
inline_disable_comment
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def inline_disable_comment
|
|
24
|
+
eol = position(@offense.line - 1, @offense.source_line.length, @offense.source_line)
|
|
25
|
+
[text_edit(eol, inline_comment_text)]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def inline_comment_text
|
|
29
|
+
if @offense.source_line.include?(' # rubocop:disable ')
|
|
30
|
+
",#{@offense.cop_name}"
|
|
31
|
+
else
|
|
32
|
+
" # rubocop:disable #{@offense.cop_name}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def block_disable_comments(range)
|
|
37
|
+
full_line_range = range_by_lines(range)
|
|
38
|
+
leading_whitespace = full_line_range.source_line[/^\s*/].to_s
|
|
39
|
+
[
|
|
40
|
+
disable_edit(full_line_range.first_line, leading_whitespace),
|
|
41
|
+
enable_edit(full_line_range, leading_whitespace)
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def disable_edit(first_line, leading_whitespace)
|
|
46
|
+
position = position(first_line - 1, 0, '')
|
|
47
|
+
text_edit(position, "#{leading_whitespace}# rubocop:disable #{@offense.cop_name}\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def enable_edit(full_line_range, leading_whitespace)
|
|
51
|
+
last_line = full_line_range.last_line
|
|
52
|
+
last_line_text = full_line_range.source_buffer.source_line(last_line)
|
|
53
|
+
position = position(last_line - 1, last_line_text.length, last_line_text)
|
|
54
|
+
text_edit(position, "\n#{leading_whitespace}# rubocop:enable #{@offense.cop_name}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def text_edit(position, new_text)
|
|
58
|
+
range = LanguageServer::Protocol::Interface::Range.new(start: position, end: position)
|
|
59
|
+
LanguageServer::Protocol::Interface::TextEdit.new(range: range, new_text: new_text)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def position(line, utf8_index, line_text)
|
|
63
|
+
LanguageServer::Protocol::Interface::Position.new(
|
|
64
|
+
line: line,
|
|
65
|
+
character: position_character(utf8_index, line_text)
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def position_character(utf8_index, line_text)
|
|
70
|
+
str = line_text[0, utf8_index]
|
|
71
|
+
if @document_encoding == Encoding::UTF_16LE || @document_encoding.nil?
|
|
72
|
+
str.length + str.b.count("\xf0-\xff".b)
|
|
73
|
+
else
|
|
74
|
+
str.length
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def multiline_literal_range
|
|
79
|
+
return unless @processed_source&.ast
|
|
80
|
+
|
|
81
|
+
offense_range = @offense.location
|
|
82
|
+
multiline_ranges&.find do |range|
|
|
83
|
+
eol_comment_would_be_inside_literal?(offense_range, range)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def multiline_ranges
|
|
88
|
+
@processed_source.ast.each_node.filter_map do |node|
|
|
89
|
+
if surrounding_heredoc?(node)
|
|
90
|
+
heredoc_range(node)
|
|
91
|
+
elsif string_continuation?(node)
|
|
92
|
+
range_by_lines(node.source_range)
|
|
93
|
+
elsif surrounding_percent_array?(node) || multiline_string?(node)
|
|
94
|
+
node.source_range
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def eol_comment_would_be_inside_literal?(offense_range, literal_range)
|
|
100
|
+
offense_line = offense_range.line
|
|
101
|
+
offense_line >= literal_range.first_line && offense_line < literal_range.last_line
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def surrounding_heredoc?(node)
|
|
105
|
+
node.any_str_type? && node.heredoc?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def heredoc_range(node)
|
|
109
|
+
node.source_range.join(node.loc.heredoc_end)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def surrounding_percent_array?(node)
|
|
113
|
+
node.array_type? && node.percent_literal?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def string_continuation?(node)
|
|
117
|
+
node.any_str_type? && node.source.match?(/\\\s*$/)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def multiline_string?(node)
|
|
121
|
+
node.dstr_type? && node.multiline?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def range_by_lines(range)
|
|
125
|
+
begin_of_first_line = range.begin_pos - range.column
|
|
126
|
+
|
|
127
|
+
last_line = range.source_buffer.source_line(range.last_line)
|
|
128
|
+
last_line_offset = last_line.length - range.last_column
|
|
129
|
+
end_of_last_line = range.end_pos + last_line_offset
|
|
130
|
+
|
|
131
|
+
Parser::Source::Range.new(range.source_buffer, begin_of_first_line, end_of_last_line)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
data/lib/rubocop/lsp/routes.rb
CHANGED
|
@@ -51,7 +51,7 @@ module RuboCop
|
|
|
51
51
|
capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
|
|
52
52
|
document_formatting_provider: true,
|
|
53
53
|
text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
|
|
54
|
-
change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::
|
|
54
|
+
change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::INCREMENTAL,
|
|
55
55
|
open_close: true
|
|
56
56
|
)
|
|
57
57
|
)
|
|
@@ -61,9 +61,16 @@ module RuboCop
|
|
|
61
61
|
|
|
62
62
|
handle 'initialized' do |_request|
|
|
63
63
|
version = RuboCop::Version::STRING
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
# Only one JIT can be enabled at the same time, since YJIT and ZJIT are mutually exclusive.
|
|
65
|
+
jit = if Object.const_defined?('RubyVM::YJIT') && RubyVM::YJIT.enabled?
|
|
66
|
+
'+YJIT'
|
|
67
|
+
elsif Object.const_defined?('RubyVM::ZJIT') && RubyVM::ZJIT.enabled?
|
|
68
|
+
'+ZJIT'
|
|
69
|
+
else
|
|
70
|
+
''
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Logger.log("RuboCop #{version} language server#{jit} initialized, PID #{Process.pid}")
|
|
67
74
|
end
|
|
68
75
|
|
|
69
76
|
handle 'shutdown' do |request|
|
|
@@ -76,7 +83,12 @@ module RuboCop
|
|
|
76
83
|
|
|
77
84
|
handle 'textDocument/didChange' do |request|
|
|
78
85
|
params = request[:params]
|
|
79
|
-
|
|
86
|
+
file_uri = params[:textDocument][:uri]
|
|
87
|
+
text = @text_cache[file_uri]
|
|
88
|
+
params[:contentChanges].each do |content|
|
|
89
|
+
text = change_text(text, content[:text], content[:range])
|
|
90
|
+
end
|
|
91
|
+
result = diagnostic(file_uri, text)
|
|
80
92
|
@server.write(result)
|
|
81
93
|
end
|
|
82
94
|
|
|
@@ -198,13 +210,13 @@ module RuboCop
|
|
|
198
210
|
|
|
199
211
|
return [] if new_text == text
|
|
200
212
|
|
|
201
|
-
[
|
|
213
|
+
[{
|
|
202
214
|
newText: new_text,
|
|
203
215
|
range: {
|
|
204
216
|
start: { line: 0, character: 0 },
|
|
205
217
|
end: { line: text.count("\n") + 1, character: 0 }
|
|
206
218
|
}
|
|
207
|
-
]
|
|
219
|
+
}]
|
|
208
220
|
end
|
|
209
221
|
|
|
210
222
|
def diagnostic(file_uri, text)
|
|
@@ -219,6 +231,30 @@ module RuboCop
|
|
|
219
231
|
}
|
|
220
232
|
end
|
|
221
233
|
|
|
234
|
+
def change_text(orig_text, text, range)
|
|
235
|
+
return text unless range
|
|
236
|
+
|
|
237
|
+
start_pos = text_pos(orig_text, range[:start])
|
|
238
|
+
end_pos = text_pos(orig_text, range[:end])
|
|
239
|
+
text_bin = orig_text.b
|
|
240
|
+
text_bin[start_pos...end_pos] = text.b
|
|
241
|
+
text_bin.force_encoding(orig_text.encoding)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def text_pos(text, range)
|
|
245
|
+
line = range[:line]
|
|
246
|
+
char = range[:character]
|
|
247
|
+
pos = 0
|
|
248
|
+
text.each_line.with_index do |l, i|
|
|
249
|
+
if i == line
|
|
250
|
+
pos += l.encode('utf-16be').b[0, char * 2].encode('utf-8', 'utf-16be').bytesize
|
|
251
|
+
return pos
|
|
252
|
+
end
|
|
253
|
+
pos += l.bytesize
|
|
254
|
+
end
|
|
255
|
+
pos
|
|
256
|
+
end
|
|
257
|
+
|
|
222
258
|
def convert_file_uri_to_path(uri)
|
|
223
259
|
URI.decode_www_form_component(uri.delete_prefix('file://'))
|
|
224
260
|
end
|
data/lib/rubocop/lsp/runtime.rb
CHANGED
|
@@ -23,7 +23,6 @@ module RuboCop
|
|
|
23
23
|
RuboCop::LSP.enable
|
|
24
24
|
|
|
25
25
|
@runner = RuboCop::Lsp::StdinRunner.new(config_store)
|
|
26
|
-
@cop_registry = RuboCop::Cop::Registry.global.to_h
|
|
27
26
|
|
|
28
27
|
@safe_autocorrect = true
|
|
29
28
|
@lint_mode = false
|
|
@@ -49,15 +48,25 @@ module RuboCop
|
|
|
49
48
|
diagnostic_options[:only] = config_only_options if @lint_mode || @layout_mode
|
|
50
49
|
|
|
51
50
|
@runner.run(path, text, diagnostic_options, prism_result: prism_result)
|
|
51
|
+
processed_source = @runner.processed_source
|
|
52
|
+
config = @runner.config_for_working_directory
|
|
52
53
|
@runner.offenses.map do |offense|
|
|
53
|
-
|
|
54
|
-
document_encoding, offense, path, @cop_registry[offense.cop_name]&.first
|
|
55
|
-
).to_lsp_diagnostic(@runner.config_for_working_directory)
|
|
54
|
+
build_diagnostic(offense, path, document_encoding, processed_source, config)
|
|
56
55
|
end
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
private
|
|
60
59
|
|
|
60
|
+
def build_diagnostic(offense, path, document_encoding, processed_source, config)
|
|
61
|
+
Diagnostic.new(
|
|
62
|
+
document_encoding,
|
|
63
|
+
offense,
|
|
64
|
+
path,
|
|
65
|
+
RuboCop::Cop::Registry.global.find_by_cop_name(offense.cop_name),
|
|
66
|
+
processed_source
|
|
67
|
+
).to_lsp_diagnostic(config)
|
|
68
|
+
end
|
|
69
|
+
|
|
61
70
|
def config_only_options
|
|
62
71
|
only_options = []
|
|
63
72
|
only_options << 'Lint' if @lint_mode
|
|
@@ -17,7 +17,7 @@ module RuboCop
|
|
|
17
17
|
class StdinRunner < RuboCop::Runner
|
|
18
18
|
class ConfigurationError < StandardError; end
|
|
19
19
|
|
|
20
|
-
attr_reader :offenses, :config_for_working_directory
|
|
20
|
+
attr_reader :offenses, :config_for_working_directory, :processed_source
|
|
21
21
|
|
|
22
22
|
DEFAULT_RUBOCOP_OPTIONS = {
|
|
23
23
|
stderr: true,
|
|
@@ -40,12 +40,12 @@ module RuboCop
|
|
|
40
40
|
super(@options, config_store)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# rubocop:disable Metrics/MethodLength
|
|
44
43
|
def run(path, contents, options, prism_result: nil)
|
|
45
44
|
@options = options.merge(DEFAULT_RUBOCOP_OPTIONS)
|
|
46
45
|
@options[:stdin] = contents
|
|
47
46
|
|
|
48
47
|
@prism_result = prism_result
|
|
48
|
+
@processed_source = nil
|
|
49
49
|
|
|
50
50
|
@offenses = []
|
|
51
51
|
@warnings = []
|
|
@@ -54,22 +54,7 @@ module RuboCop
|
|
|
54
54
|
super([path])
|
|
55
55
|
|
|
56
56
|
raise Interrupt if aborting?
|
|
57
|
-
rescue RuboCop::Runner::InfiniteCorrectionLoop => e
|
|
58
|
-
if defined?(::RubyLsp::Requests::Formatting::Error)
|
|
59
|
-
raise ::RubyLsp::Requests::Formatting::Error, e.message
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
raise e
|
|
63
|
-
rescue RuboCop::ValidationError => e
|
|
64
|
-
raise ConfigurationError, e.message
|
|
65
|
-
rescue StandardError => e
|
|
66
|
-
if defined?(::RubyLsp::Requests::Formatting::Error)
|
|
67
|
-
raise ::RubyLsp::Requests::Support::InternalRuboCopError, e
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
raise e
|
|
71
57
|
end
|
|
72
|
-
# rubocop:enable Metrics/MethodLength
|
|
73
58
|
|
|
74
59
|
def formatted_source
|
|
75
60
|
@options[:stdin]
|
|
@@ -80,6 +65,12 @@ module RuboCop
|
|
|
80
65
|
def file_finished(_file, offenses)
|
|
81
66
|
@offenses = offenses
|
|
82
67
|
end
|
|
68
|
+
|
|
69
|
+
def do_inspection_loop(file)
|
|
70
|
+
source, offenses = super
|
|
71
|
+
@processed_source = source
|
|
72
|
+
[source, offenses]
|
|
73
|
+
end
|
|
83
74
|
end
|
|
84
75
|
end
|
|
85
76
|
end
|
|
@@ -11,6 +11,7 @@ module RuboCop
|
|
|
11
11
|
KEYWORDS = {
|
|
12
12
|
encoding: '(?:en)?coding',
|
|
13
13
|
frozen_string_literal: 'frozen[_-]string[_-]literal',
|
|
14
|
+
rbs_inline: 'rbs_inline',
|
|
14
15
|
shareable_constant_value: 'shareable[_-]constant[_-]value',
|
|
15
16
|
typed: 'typed'
|
|
16
17
|
}.freeze
|
|
@@ -36,6 +37,7 @@ module RuboCop
|
|
|
36
37
|
def any?
|
|
37
38
|
frozen_string_literal_specified? ||
|
|
38
39
|
encoding_specified? ||
|
|
40
|
+
rbs_inline_specified? ||
|
|
39
41
|
shareable_constant_value_specified? ||
|
|
40
42
|
typed_specified?
|
|
41
43
|
end
|
|
@@ -60,6 +62,10 @@ module RuboCop
|
|
|
60
62
|
[true, false].include?(frozen_string_literal)
|
|
61
63
|
end
|
|
62
64
|
|
|
65
|
+
def valid_rbs_inline_value?
|
|
66
|
+
%w[enabled disabled].include?(extract_rbs_inline_value)
|
|
67
|
+
end
|
|
68
|
+
|
|
63
69
|
def valid_shareable_constant_value?
|
|
64
70
|
%w[none literal experimental_everything experimental_copy].include?(shareable_constant_value)
|
|
65
71
|
end
|
|
@@ -105,6 +111,10 @@ module RuboCop
|
|
|
105
111
|
specified?(encoding)
|
|
106
112
|
end
|
|
107
113
|
|
|
114
|
+
def rbs_inline_specified?
|
|
115
|
+
valid_rbs_inline_value?
|
|
116
|
+
end
|
|
117
|
+
|
|
108
118
|
# Was the Sorbet `typed` sigil specified?
|
|
109
119
|
#
|
|
110
120
|
# @return [Boolean]
|
|
@@ -203,6 +213,9 @@ module RuboCop
|
|
|
203
213
|
match(KEYWORDS[:frozen_string_literal])
|
|
204
214
|
end
|
|
205
215
|
|
|
216
|
+
# Emacs comments cannot specify RBS::inline behavior.
|
|
217
|
+
def extract_rbs_inline_value; end
|
|
218
|
+
|
|
206
219
|
def extract_shareable_constant_value
|
|
207
220
|
match(KEYWORDS[:shareable_constant_value])
|
|
208
221
|
end
|
|
@@ -242,6 +255,9 @@ module RuboCop
|
|
|
242
255
|
# Vim comments cannot specify frozen string literal behavior.
|
|
243
256
|
def frozen_string_literal; end
|
|
244
257
|
|
|
258
|
+
# Vim comments cannot specify RBS::inline behavior.
|
|
259
|
+
def extract_rbs_inline_value; end
|
|
260
|
+
|
|
245
261
|
# Vim comments cannot specify shareable constant values behavior.
|
|
246
262
|
def shareable_constant_value; end
|
|
247
263
|
|
|
@@ -296,6 +312,10 @@ module RuboCop
|
|
|
296
312
|
extract(/\A\s*#\s*#{KEYWORDS[:frozen_string_literal]}:\s*#{TOKEN}\s*\z/io)
|
|
297
313
|
end
|
|
298
314
|
|
|
315
|
+
def extract_rbs_inline_value
|
|
316
|
+
extract(/\A\s*#\s*#{KEYWORDS[:rbs_inline]}:\s*#{TOKEN}\s*\z/io)
|
|
317
|
+
end
|
|
318
|
+
|
|
299
319
|
def extract_shareable_constant_value
|
|
300
320
|
extract(/\A\s*#\s*#{KEYWORDS[:shareable_constant_value]}:\s*#{TOKEN}\s*\z/io)
|
|
301
321
|
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'mcp'
|
|
5
|
+
|
|
6
|
+
required_mcp_version = '0.6.0'
|
|
7
|
+
|
|
8
|
+
if Gem::Version.new(required_mcp_version) > Gem::Version.new(MCP::VERSION)
|
|
9
|
+
# While `mcp` is not a runtime dependency, users may have an outdated version installed.
|
|
10
|
+
warn <<~MESSAGE
|
|
11
|
+
Error: `mcp` gem version #{MCP::VERSION} was loaded, but `rubocop --mcp` requires #{required_mcp_version}.
|
|
12
|
+
- If you're using Bundler and don't yet have `gem 'mcp'` as a dependency, add it now.
|
|
13
|
+
- If you're using Bundler and already have `gem 'mcp'` as a dependency, update it to the most recent version.
|
|
14
|
+
- If you don't use Bundler, run `gem update mcp`.
|
|
15
|
+
MESSAGE
|
|
16
|
+
exit!
|
|
17
|
+
end
|
|
18
|
+
rescue LoadError => e
|
|
19
|
+
raise unless e.path == 'mcp'
|
|
20
|
+
|
|
21
|
+
warn <<~MESSAGE
|
|
22
|
+
Error: Unable to load `mcp` gem. Add `gem 'mcp', '~> 0.6'` to your Gemfile, or run `gem install mcp`.
|
|
23
|
+
MESSAGE
|
|
24
|
+
|
|
25
|
+
exit!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require_relative '../lsp'
|
|
29
|
+
require_relative '../lsp/runtime'
|
|
30
|
+
|
|
31
|
+
module RuboCop
|
|
32
|
+
module MCP
|
|
33
|
+
# RuboCop MCP Server.
|
|
34
|
+
# @api private
|
|
35
|
+
class Server
|
|
36
|
+
def initialize(config_store)
|
|
37
|
+
@config_store = config_store
|
|
38
|
+
@runtime = RuboCop::LSP::Runtime.new(@config_store)
|
|
39
|
+
@options = {}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def start
|
|
43
|
+
# No `protocol_version` is specified because draft feature by default can be used.
|
|
44
|
+
server = ::MCP::Server.new(
|
|
45
|
+
name: 'rubocop_mcp_server',
|
|
46
|
+
version: RuboCop::Version::STRING,
|
|
47
|
+
tools: [inspection_tool, autocorrection_tool]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
::MCP::Server::Transports::StdioTransport.new(server).open
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def inspection_tool
|
|
56
|
+
build_tool(
|
|
57
|
+
name: 'rubocop_inspection',
|
|
58
|
+
description: 'Inspect Ruby code for offenses. ' \
|
|
59
|
+
'Provide `source_code` to check inline code or `path` to check files.',
|
|
60
|
+
title: "RuboCop's inspection",
|
|
61
|
+
destructive_hint: false,
|
|
62
|
+
idempotent_hint: true,
|
|
63
|
+
read_only_hint: true,
|
|
64
|
+
safety_required: false
|
|
65
|
+
) do |path, source_code|
|
|
66
|
+
run_inspection(path, source_code)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def autocorrection_tool
|
|
71
|
+
build_tool(
|
|
72
|
+
name: 'rubocop_autocorrection',
|
|
73
|
+
description: 'Autocorrect RuboCop offenses in Ruby code. ' \
|
|
74
|
+
'Provide `source_code` to correct inline code or `path` to correct files. ' \
|
|
75
|
+
'Set `safety` to false to include unsafe corrections.',
|
|
76
|
+
title: "RuboCop's autocorrection",
|
|
77
|
+
destructive_hint: true,
|
|
78
|
+
idempotent_hint: false,
|
|
79
|
+
read_only_hint: false,
|
|
80
|
+
safety_required: true
|
|
81
|
+
) do |path, source_code, safety|
|
|
82
|
+
run_autocorrection(path, source_code, safety)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def run_inspection(path, source_code)
|
|
87
|
+
if source_code
|
|
88
|
+
offenses = @runtime.offenses(path || 'example.rb', source_code, source_code.encoding)
|
|
89
|
+
offenses.to_json
|
|
90
|
+
else
|
|
91
|
+
process_files(path, filter_empty: true) do |file, source|
|
|
92
|
+
offenses = @runtime.offenses(file, source, source.encoding)
|
|
93
|
+
|
|
94
|
+
{ path: PathUtil.relative_path(file), offenses: offenses }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def run_autocorrection(path, source_code, safety)
|
|
100
|
+
command = safety ? 'rubocop.formatAutocorrects' : 'rubocop.formatAutocorrectsAll'
|
|
101
|
+
|
|
102
|
+
if source_code
|
|
103
|
+
@runtime.format(path || 'example.rb', source_code, command: command).tap do |corrected|
|
|
104
|
+
write_file(path, corrected) if path
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
process_files(path) do |file, source|
|
|
108
|
+
@runtime.format(file, source, command: command).then do |corrected|
|
|
109
|
+
write_file(file, corrected)
|
|
110
|
+
|
|
111
|
+
{ path: PathUtil.relative_path(file), corrected: source != corrected }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def process_files(path, filter_empty: false)
|
|
118
|
+
target_finder = RuboCop::TargetFinder.new(@config_store, @options)
|
|
119
|
+
target_files = target_finder.find(path ? [path] : [], :only_recognized_file_types)
|
|
120
|
+
all_files = target_files.map { |file| yield(file, read_file(file)) }
|
|
121
|
+
files = filter_empty ? all_files.reject { |f| f[:offenses]&.empty? } : all_files
|
|
122
|
+
|
|
123
|
+
{ files: files, summary: build_summary(target_files, all_files) }.to_json
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def read_file(file)
|
|
127
|
+
config = @config_store.for_file(file)
|
|
128
|
+
RuboCop::ProcessedSource.from_file(
|
|
129
|
+
file, config.target_ruby_version, parser_engine: config.parser_engine
|
|
130
|
+
).raw_source
|
|
131
|
+
rescue Errno::ENOENT
|
|
132
|
+
raise RuboCop::Error, "No such file or directory: #{file}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def write_file(file, content)
|
|
136
|
+
File.write(file, content)
|
|
137
|
+
rescue Errno::EACCES
|
|
138
|
+
raise RuboCop::Error, "Permission denied: #{file}"
|
|
139
|
+
rescue Errno::ENOSPC
|
|
140
|
+
raise RuboCop::Error, "No space left on device: #{file}"
|
|
141
|
+
rescue Errno::EROFS
|
|
142
|
+
raise RuboCop::Error, "Read-only file system: #{file}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# NOTE: It is useful for RuboCop's result summary to be shown in the LLM's responses
|
|
146
|
+
# during interactions, so the summary is returned in a form that is easy for the LLM
|
|
147
|
+
# to reason about. Since LLM execution is non-deterministic, it is also sensible to
|
|
148
|
+
# compute the summary deterministically at this stage.
|
|
149
|
+
def build_summary(target_files, files)
|
|
150
|
+
summary = { target_file_count: target_files.count }
|
|
151
|
+
if files.first&.key?(:offenses)
|
|
152
|
+
summary[:offense_count] = files.sum { |f| f[:offenses].size }
|
|
153
|
+
else
|
|
154
|
+
summary[:corrected_file_count] = files.count { |f| f[:corrected] }
|
|
155
|
+
end
|
|
156
|
+
summary
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
160
|
+
def build_tool(
|
|
161
|
+
name:, description:,
|
|
162
|
+
title:, destructive_hint:, idempotent_hint:, read_only_hint:, safety_required:
|
|
163
|
+
)
|
|
164
|
+
if safety_required
|
|
165
|
+
safety_property = { safety: { type: 'boolean' } }
|
|
166
|
+
required = ['safety']
|
|
167
|
+
else
|
|
168
|
+
safety_property = {}
|
|
169
|
+
required = nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
::MCP::Tool.define(
|
|
173
|
+
name: name,
|
|
174
|
+
description: description,
|
|
175
|
+
input_schema: {
|
|
176
|
+
properties: {
|
|
177
|
+
path: { type: 'string' },
|
|
178
|
+
source_code: { type: 'string' }
|
|
179
|
+
}.merge(safety_property),
|
|
180
|
+
required: required
|
|
181
|
+
}.compact,
|
|
182
|
+
annotations: {
|
|
183
|
+
title: title,
|
|
184
|
+
destructive_hint: destructive_hint,
|
|
185
|
+
idempotent_hint: idempotent_hint,
|
|
186
|
+
open_world_hint: false,
|
|
187
|
+
read_only_hint: read_only_hint
|
|
188
|
+
}
|
|
189
|
+
) do |path: nil, source_code: nil, safety: true|
|
|
190
|
+
result = yield(path, source_code, safety)
|
|
191
|
+
|
|
192
|
+
::MCP::Tool::Response.new([{ type: 'text', text: result }])
|
|
193
|
+
rescue RuboCop::Error => e
|
|
194
|
+
::MCP::Tool::Response.new([{ type: 'text', text: e.message }], error: true)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|