rubocop 1.91.0 → 1.92.2
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/README.md +1 -1
- data/config/default.yml +4 -1
- data/lib/rubocop/cached_data.rb +47 -7
- data/lib/rubocop/changed_files.rb +35 -13
- data/lib/rubocop/cli/command/cop_names.rb +57 -0
- data/lib/rubocop/cli/command/execute_runner.rb +2 -2
- data/lib/rubocop/cli/command/explain.rb +34 -0
- data/lib/rubocop/cli/command/show_cops.rb +6 -0
- data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
- data/lib/rubocop/cli.rb +3 -2
- data/lib/rubocop/comment_config.rb +17 -8
- data/lib/rubocop/config.rb +0 -4
- data/lib/rubocop/config_loader.rb +6 -3
- data/lib/rubocop/config_loader_resolver.rb +37 -12
- data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
- data/lib/rubocop/config_obsoletion.rb +9 -0
- data/lib/rubocop/config_validator.rb +11 -0
- data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
- data/lib/rubocop/cop/badge.rb +1 -1
- data/lib/rubocop/cop/base.rb +42 -21
- data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
- data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
- data/lib/rubocop/cop/cop_documentation.rb +81 -0
- data/lib/rubocop/cop/cop_explanation.rb +140 -0
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
- data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
- data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
- data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
- data/lib/rubocop/cop/documentation.rb +1 -1
- data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
- data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
- data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
- data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
- data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
- data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
- data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
- data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
- data/lib/rubocop/cop/layout/class_structure.rb +29 -7
- data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
- data/lib/rubocop/cop/layout/dot_position.rb +25 -15
- data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
- data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
- data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
- data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
- data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
- data/lib/rubocop/cop/layout/end_alignment.rb +21 -25
- data/lib/rubocop/cop/layout/extra_spacing.rb +21 -6
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
- data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
- data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
- data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
- data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
- data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
- data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
- data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
- data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
- data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
- data/lib/rubocop/cop/layout/line_length.rb +176 -26
- data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
- data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
- data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
- data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
- data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
- data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +16 -3
- data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
- data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
- data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
- data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
- data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +16 -1
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +32 -9
- data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
- data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
- data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
- data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
- data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
- data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
- data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
- data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
- data/lib/rubocop/cop/lint/loop.rb +24 -2
- data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
- data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
- data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
- data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +39 -9
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
- data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
- data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
- data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
- data/lib/rubocop/cop/lint/void.rb +8 -1
- data/lib/rubocop/cop/metrics/class_length.rb +4 -2
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
- data/lib/rubocop/cop/metrics/module_length.rb +4 -2
- data/lib/rubocop/cop/mixin/alignment.rb +4 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
- data/lib/rubocop/cop/mixin/code_length.rb +10 -2
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
- data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
- data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
- data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +41 -6
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
- data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
- data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
- data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
- data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
- data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
- data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
- data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
- data/lib/rubocop/cop/mixin/statement_modifier.rb +39 -5
- data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
- data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
- data/lib/rubocop/cop/mixin.rb +2 -0
- data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
- data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
- data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
- data/lib/rubocop/cop/offense.rb +59 -3
- data/lib/rubocop/cop/registry.rb +1 -1
- data/lib/rubocop/cop/security/json_load.rb +16 -4
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
- data/lib/rubocop/cop/style/alias.rb +18 -5
- data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
- data/lib/rubocop/cop/style/and_or.rb +37 -10
- data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
- data/lib/rubocop/cop/style/attr.rb +3 -4
- data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
- data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
- data/lib/rubocop/cop/style/case_equality.rb +14 -3
- data/lib/rubocop/cop/style/case_like_if.rb +6 -3
- data/lib/rubocop/cop/style/class_and_module_children.rb +57 -16
- data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
- data/lib/rubocop/cop/style/collection_querying.rb +79 -46
- data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
- data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
- data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
- data/lib/rubocop/cop/style/double_negation.rb +11 -0
- data/lib/rubocop/cop/style/empty_literal.rb +1 -1
- data/lib/rubocop/cop/style/empty_method.rb +3 -1
- data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
- data/lib/rubocop/cop/style/endless_method.rb +104 -11
- data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
- data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
- data/lib/rubocop/cop/style/float_division.rb +37 -2
- data/lib/rubocop/cop/style/for.rb +53 -2
- data/lib/rubocop/cop/style/format_string.rb +28 -6
- data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
- data/lib/rubocop/cop/style/guard_clause.rb +31 -22
- data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
- data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
- data/lib/rubocop/cop/style/hash_lookup_method.rb +42 -9
- data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
- data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
- data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
- data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
- data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
- data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
- data/lib/rubocop/cop/style/lambda.rb +34 -1
- data/lib/rubocop/cop/style/lambda_call.rb +7 -2
- data/lib/rubocop/cop/style/map_into_array.rb +1 -2
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +26 -2
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
- data/lib/rubocop/cop/style/method_def_parentheses.rb +38 -4
- data/lib/rubocop/cop/style/missing_else.rb +7 -1
- data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
- data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
- data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
- data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
- data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
- data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
- data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
- data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
- data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
- data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
- data/lib/rubocop/cop/style/next.rb +24 -10
- data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
- data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
- data/lib/rubocop/cop/style/not.rb +10 -5
- data/lib/rubocop/cop/style/numeric_predicate.rb +28 -5
- data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
- data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
- data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
- data/lib/rubocop/cop/style/raise_args.rb +80 -11
- data/lib/rubocop/cop/style/reduce_to_hash.rb +47 -33
- data/lib/rubocop/cop/style/redundant_begin.rb +45 -5
- data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
- data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
- data/lib/rubocop/cop/style/redundant_exception.rb +27 -11
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
- data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
- data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
- data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
- data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
- data/lib/rubocop/cop/style/redundant_self.rb +9 -1
- data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
- data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
- data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
- data/lib/rubocop/cop/style/require_order.rb +25 -8
- data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
- data/lib/rubocop/cop/style/return_nil.rb +17 -2
- data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
- data/lib/rubocop/cop/style/sample.rb +9 -8
- data/lib/rubocop/cop/style/self_assignment.rb +12 -15
- data/lib/rubocop/cop/style/semicolon.rb +16 -5
- data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
- data/lib/rubocop/cop/style/single_line_methods.rb +42 -4
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
- data/lib/rubocop/cop/style/string_concatenation.rb +21 -4
- data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
- data/lib/rubocop/cop/style/super_arguments.rb +3 -0
- data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
- data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
- data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
- data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
- data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
- data/lib/rubocop/cop/team.rb +10 -5
- data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
- data/lib/rubocop/cop/variable_force/variable.rb +14 -1
- data/lib/rubocop/formatter/base_formatter.rb +22 -0
- data/lib/rubocop/formatter/formatter_set.rb +4 -0
- data/lib/rubocop/formatter/json_formatter.rb +71 -1
- data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
- data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
- data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
- data/lib/rubocop/lsp/routes.rb +23 -23
- data/lib/rubocop/lsp/runtime.rb +47 -11
- data/lib/rubocop/lsp/stdin_runner.rb +11 -5
- data/lib/rubocop/lsp.rb +5 -2
- data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
- data/lib/rubocop/mcp/explain_tool.rb +68 -0
- data/lib/rubocop/mcp/scope.rb +65 -0
- data/lib/rubocop/mcp/server.rb +184 -76
- data/lib/rubocop/mcp/source_files.rb +35 -0
- data/lib/rubocop/offense_limit.rb +30 -0
- data/lib/rubocop/options.rb +45 -10
- data/lib/rubocop/plugin/configuration_integrator.rb +36 -8
- data/lib/rubocop/remote_config.rb +4 -1
- data/lib/rubocop/result_cache.rb +20 -13
- data/lib/rubocop/runner.rb +110 -42
- data/lib/rubocop/server/cache.rb +6 -6
- data/lib/rubocop/target_ruby.rb +21 -9
- data/lib/rubocop/util.rb +69 -0
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +5 -0
- metadata +16 -5
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module MCP
|
|
5
|
+
# What a request asks RuboCop to look at: which cops run, as `--only` and
|
|
6
|
+
# `--except` choose them, and, when checking files, whether to stick to the
|
|
7
|
+
# ones git says changed, as `--changed` does.
|
|
8
|
+
# @api private
|
|
9
|
+
class Scope
|
|
10
|
+
PROPERTIES = {
|
|
11
|
+
only: {
|
|
12
|
+
type: 'array', items: { type: 'string' },
|
|
13
|
+
description: 'Run only these cops or departments, ' \
|
|
14
|
+
'such as `Style/StringLiterals` or `Lint`.'
|
|
15
|
+
},
|
|
16
|
+
except: {
|
|
17
|
+
type: 'array', items: { type: 'string' },
|
|
18
|
+
description: 'Run every cop except these cops or departments.'
|
|
19
|
+
},
|
|
20
|
+
changed: {
|
|
21
|
+
type: %w[boolean string],
|
|
22
|
+
description: 'Check only the files git says changed: `true` for changes since the ' \
|
|
23
|
+
'last commit, or a revision to compare against, such as `main`.'
|
|
24
|
+
}
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# An empty list would select no cops at all rather than leave the choice
|
|
28
|
+
# alone, so it counts as no list.
|
|
29
|
+
#
|
|
30
|
+
# @raise [RuboCop::Error] for a scope the command line would refuse
|
|
31
|
+
def initialize(inline:, only: nil, except: nil, changed: nil)
|
|
32
|
+
@only = only if only&.any?
|
|
33
|
+
@except = except if except&.any?
|
|
34
|
+
@changed = changed == true ? ChangedFiles::DEFAULT_REVISION : changed
|
|
35
|
+
validate(inline)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Hash] the cop selection, among the options the LSP runtime takes
|
|
39
|
+
def cop_options
|
|
40
|
+
{ only: @only, except: @except }.compact
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def select_files(files)
|
|
44
|
+
@changed ? ChangedFiles.new(@changed).filter(files) : files
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Refuses what the command line refuses for the same options. Cop names
|
|
50
|
+
# are left to the runner, which checks them the way `--only` does: short
|
|
51
|
+
# names allowed, and only once the configuration naming a plugin's cops
|
|
52
|
+
# has been loaded.
|
|
53
|
+
def validate(inline)
|
|
54
|
+
raise Error, '`changed` only applies when checking files.' if inline && @changed
|
|
55
|
+
raise Error, '`changed` takes `true` or a git revision.' if @changed == ''
|
|
56
|
+
|
|
57
|
+
validator = OptionsValidator.new(cop_options)
|
|
58
|
+
if validator.only_includes_redundant_disable?
|
|
59
|
+
raise Error, 'Lint/RedundantCopDisableDirective cannot be used with `only`.'
|
|
60
|
+
end
|
|
61
|
+
raise Error, 'Syntax checking cannot be turned off.' if validator.except_syntax?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/rubocop/mcp/server.rb
CHANGED
|
@@ -19,7 +19,7 @@ rescue LoadError => e
|
|
|
19
19
|
raise unless e.path == 'mcp'
|
|
20
20
|
|
|
21
21
|
warn <<~MESSAGE
|
|
22
|
-
Error: Unable to load `mcp` gem. Add `gem 'mcp'
|
|
22
|
+
Error: Unable to load `mcp` gem. Add `gem 'mcp'` to your Gemfile, or run `gem install mcp`.
|
|
23
23
|
MESSAGE
|
|
24
24
|
|
|
25
25
|
exit!
|
|
@@ -27,16 +27,59 @@ end
|
|
|
27
27
|
|
|
28
28
|
require_relative '../lsp'
|
|
29
29
|
require_relative '../lsp/runtime'
|
|
30
|
+
require_relative 'autocorrection_request'
|
|
31
|
+
require_relative 'explain_tool'
|
|
32
|
+
require_relative 'scope'
|
|
33
|
+
require_relative 'source_files'
|
|
30
34
|
|
|
31
35
|
module RuboCop
|
|
32
36
|
module MCP
|
|
33
37
|
# RuboCop MCP Server.
|
|
34
38
|
# @api private
|
|
35
39
|
class Server
|
|
40
|
+
INSPECTION_DESCRIPTION =
|
|
41
|
+
'Inspect Ruby code for offenses. ' \
|
|
42
|
+
'Provide `source_code` to check inline code or `path` to check files. ' \
|
|
43
|
+
'Either way the result lists offenses per file, with a summary, and the ' \
|
|
44
|
+
'offenses use the `rubocop --format json` format, with 1-based lines. ' \
|
|
45
|
+
'`correctable` says whether `rubocop_autocorrection` fixes an offense, ' \
|
|
46
|
+
'and one whose `correction` is not `safe` needs `safety` set to false. ' \
|
|
47
|
+
'One with a `correction` that is not `correctable` is held back while code is ' \
|
|
48
|
+
'being written, and only fixed with `contextual` set to true. ' \
|
|
49
|
+
'`max_offenses_per_cop` caps what each cop reports, and the ' \
|
|
50
|
+
'summary\'s `unreported_offenses` counts what was left out. ' \
|
|
51
|
+
'A cop that crashes is listed in its file\'s `errors`; the rest still report.'
|
|
52
|
+
|
|
53
|
+
AUTOCORRECTION_DESCRIPTION =
|
|
54
|
+
'Autocorrect RuboCop offenses in Ruby code. ' \
|
|
55
|
+
'Provide `source_code` to correct inline code, which comes back corrected as ' \
|
|
56
|
+
'plain text, or `path` to correct files. ' \
|
|
57
|
+
'Set `safety` to false to include unsafe corrections, and `contextual` to true ' \
|
|
58
|
+
'to include the ones held back while code is being written, such as removing ' \
|
|
59
|
+
'an unused variable. ' \
|
|
60
|
+
'Set `dry_run` to true to preview: nothing is written, and each corrected file ' \
|
|
61
|
+
'comes with its `diff`. ' \
|
|
62
|
+
'For files, the result lists each file that was corrected or still has offenses, ' \
|
|
63
|
+
'with the offenses left in the `rubocop_inspection` format, located in the ' \
|
|
64
|
+
'corrected file. One whose `correction` is not `safe` needs `safety` set to false, ' \
|
|
65
|
+
'and one with a `correction` that is not `correctable` needs `contextual` set to ' \
|
|
66
|
+
'true. Each cop lists at most `max_offenses_per_cop` of them, 5 unless ' \
|
|
67
|
+
'set, and the summary\'s `unreported_offenses` counts the rest. ' \
|
|
68
|
+
'A cop that crashes is listed in its file\'s `errors`, and a file whose ' \
|
|
69
|
+
'corrections loop is left unchanged.'
|
|
70
|
+
|
|
36
71
|
def initialize(config_store)
|
|
37
72
|
@config_store = config_store
|
|
38
|
-
|
|
73
|
+
# An agent isn't an editor, so offenses are located and worded the way
|
|
74
|
+
# the command line does it. Only contextual corrections are held back.
|
|
75
|
+
@runtime = RuboCop::LSP::Runtime.new(@config_store, lsp_mode: false)
|
|
76
|
+
# One cop crashing on one file should not cost an agent everything else
|
|
77
|
+
# in the request, so cop errors are collected and reported per file.
|
|
78
|
+
@runtime.raise_cop_error = false
|
|
39
79
|
@options = {}
|
|
80
|
+
# Offenses are reported the way `--format json` reports them, so an agent
|
|
81
|
+
# sees the same 1-based locations and correction data either way.
|
|
82
|
+
@json_formatter = RuboCop::Formatter::JSONFormatter.new(nil)
|
|
40
83
|
end
|
|
41
84
|
|
|
42
85
|
def start
|
|
@@ -44,7 +87,7 @@ module RuboCop
|
|
|
44
87
|
server = ::MCP::Server.new(
|
|
45
88
|
name: 'rubocop_mcp_server',
|
|
46
89
|
version: RuboCop::Version::STRING,
|
|
47
|
-
tools: [inspection_tool, autocorrection_tool]
|
|
90
|
+
tools: [inspection_tool, autocorrection_tool, ExplainTool.new(@config_store).to_tool]
|
|
48
91
|
)
|
|
49
92
|
|
|
50
93
|
::MCP::Server::Transports::StdioTransport.new(server).open
|
|
@@ -55,91 +98,158 @@ module RuboCop
|
|
|
55
98
|
def inspection_tool
|
|
56
99
|
build_tool(
|
|
57
100
|
name: 'rubocop_inspection',
|
|
58
|
-
description:
|
|
59
|
-
'Provide `source_code` to check inline code or `path` to check files.',
|
|
101
|
+
description: INSPECTION_DESCRIPTION,
|
|
60
102
|
title: "RuboCop's inspection",
|
|
61
103
|
destructive_hint: false,
|
|
62
104
|
idempotent_hint: true,
|
|
63
105
|
read_only_hint: true,
|
|
64
|
-
|
|
65
|
-
) do |path, source_code|
|
|
66
|
-
run_inspection(path, source_code)
|
|
106
|
+
properties: { max_offenses_per_cop: { type: 'integer', minimum: 1 } }
|
|
107
|
+
) do |scope:, path: nil, source_code: nil, max_offenses_per_cop: nil|
|
|
108
|
+
run_inspection(path, source_code, max_offenses_per_cop, scope)
|
|
67
109
|
end
|
|
68
110
|
end
|
|
69
111
|
|
|
70
112
|
def autocorrection_tool
|
|
71
113
|
build_tool(
|
|
72
114
|
name: 'rubocop_autocorrection',
|
|
73
|
-
description:
|
|
74
|
-
'Provide `source_code` to correct inline code or `path` to correct files. ' \
|
|
75
|
-
'Set `safety` to false to include unsafe corrections.',
|
|
115
|
+
description: AUTOCORRECTION_DESCRIPTION,
|
|
76
116
|
title: "RuboCop's autocorrection",
|
|
77
117
|
destructive_hint: true,
|
|
78
118
|
idempotent_hint: false,
|
|
79
119
|
read_only_hint: false,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
120
|
+
properties: AutocorrectionRequest::PROPERTIES,
|
|
121
|
+
required: ['safety']
|
|
122
|
+
) do |scope:, path: nil, source_code: nil, **arguments|
|
|
123
|
+
run_autocorrection(path, source_code, AutocorrectionRequest.new(scope, **arguments))
|
|
83
124
|
end
|
|
84
125
|
end
|
|
85
126
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
# A limit counts across the files of one request, so every request starts
|
|
128
|
+
# from zero.
|
|
129
|
+
def run_inspection(path, source_code, max_offenses_per_cop, scope)
|
|
130
|
+
limit = OffenseLimit.new(max_offenses_per_cop) if max_offenses_per_cop
|
|
131
|
+
result =
|
|
132
|
+
if source_code
|
|
133
|
+
file = path || 'example.rb'
|
|
134
|
+
build_result([file], [inspect_source(file, source_code, limit, scope)])
|
|
135
|
+
else
|
|
136
|
+
process_files(path, scope) { |file, src| inspect_source(file, src, limit, scope) }
|
|
95
137
|
end
|
|
138
|
+
result_json(result, limit)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def inspect_source(file, source, limit, scope)
|
|
142
|
+
options = { **scope.cop_options, editing: true }
|
|
143
|
+
offenses = @runtime.raw_offenses(file, source, options: options)
|
|
144
|
+
offense_entry(file, offenses, limit).merge(problems_of_last_run(file))
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# The offenses of a file the way `--format json` lists them, capped across
|
|
148
|
+
# the files of a request.
|
|
149
|
+
def offense_entry(file, offenses, limit)
|
|
150
|
+
offenses = limit.filter(offenses) if limit
|
|
151
|
+
@json_formatter.hash_for_file(file, offenses)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def run_autocorrection(path, source_code, request)
|
|
155
|
+
return correct_inline(path, source_code, request) if source_code
|
|
156
|
+
|
|
157
|
+
result = process_files(path, request.scope) do |file, source|
|
|
158
|
+
correct_file(file, source, request)
|
|
96
159
|
end
|
|
160
|
+
result[:summary][:corrected_file_count] = result[:files].count { |file| file[:corrected] }
|
|
161
|
+
result_json(result, request.limit)
|
|
97
162
|
end
|
|
98
163
|
|
|
99
|
-
|
|
100
|
-
|
|
164
|
+
# The agent never sees stderr, so the summary says what the limit left out.
|
|
165
|
+
def result_json(result, limit)
|
|
166
|
+
unreported = limit&.elided_per_cop
|
|
167
|
+
result[:summary][:unreported_offenses] = unreported if unreported&.any?
|
|
168
|
+
result.to_json
|
|
169
|
+
end
|
|
101
170
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
171
|
+
# Cops that keep undoing each other's corrections leave the file half
|
|
172
|
+
# corrected, so it is left alone and the loop is reported instead. A file
|
|
173
|
+
# with nothing to correct is left alone too, so its modification time
|
|
174
|
+
# doesn't tell editors and agents it changed. The offenses left over are
|
|
175
|
+
# the ones an agent has to fix itself, which saves it inspecting again.
|
|
176
|
+
def correct_file(file, source, request)
|
|
177
|
+
corrected = @runtime.format(file, source, **request.format_options)
|
|
178
|
+
problems = problems_of_last_run(file)
|
|
179
|
+
looped = @runtime.errors.any?(Runner::InfiniteCorrectionLoop)
|
|
180
|
+
changed = !looped && source != corrected
|
|
181
|
+
leftovers = leftover_offenses(file, source, corrected, changed, request)
|
|
182
|
+
entry = offense_entry(file, leftovers, request.limit)
|
|
183
|
+
diff = write_or_diff(file, entry[:path], source, corrected, request) if changed
|
|
184
|
+
|
|
185
|
+
{ path: entry[:path], corrected: changed, diff: diff, **entry, **problems }.compact
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# A dry run writes nothing and returns the diff a real run would have
|
|
189
|
+
# applied, line endings included.
|
|
190
|
+
def write_or_diff(file, path, source, corrected, request)
|
|
191
|
+
if request.dry_run?
|
|
192
|
+
UnifiedDiff.new(path, source, Util.emulate_write_read_cycle(corrected)).to_s
|
|
106
193
|
else
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
194
|
+
SourceFiles.write(file, corrected)
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
197
|
+
end
|
|
110
198
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
199
|
+
# What's left is in the file as written, or as a dry run would have
|
|
200
|
+
# written it, or as it was when its corrections loop. The runner checks
|
|
201
|
+
# corrected code with LF line endings, which writing turns into CRLF on
|
|
202
|
+
# Windows, so there the file is checked again as written.
|
|
203
|
+
def leftover_offenses(file, source, corrected, changed, request)
|
|
204
|
+
on_disk = changed ? Util.emulate_write_read_cycle(corrected) : source
|
|
205
|
+
return @runtime.uncorrected_offenses if on_disk == corrected
|
|
206
|
+
|
|
207
|
+
@runtime.raw_offenses(file, on_disk, options: request.runtime_options)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Corrected inline code comes back as plain text with nowhere to mention
|
|
211
|
+
# a cop that crashed, so a crash fails the call instead of going unseen.
|
|
212
|
+
def correct_inline(path, source_code, request)
|
|
213
|
+
file = path || 'example.rb'
|
|
214
|
+
corrected = @runtime.format(file, source_code, **request.format_options)
|
|
215
|
+
errors = problems_of_last_run(file)[:errors]
|
|
216
|
+
raise RuboCop::Error, errors.join("\n") if errors
|
|
217
|
+
|
|
218
|
+
SourceFiles.write(path, corrected) if path && !request.dry_run?
|
|
219
|
+
corrected
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Collapsed the way `--format json` collapses them, which also folds the
|
|
223
|
+
# repeats from each autocorrection pass a crashing cop fails on.
|
|
224
|
+
def problems_of_last_run(file)
|
|
225
|
+
errors, warnings = [@runtime.errors, @runtime.warnings].map do |problems|
|
|
226
|
+
problems.map { |problem| Rainbow::StringUtils.uncolor(problem.to_s) }
|
|
114
227
|
end
|
|
228
|
+
@json_formatter.hash_for_problems(file, errors, warnings)
|
|
115
229
|
end
|
|
116
230
|
|
|
117
|
-
def process_files(path,
|
|
231
|
+
def process_files(path, scope)
|
|
118
232
|
target_finder = RuboCop::TargetFinder.new(@config_store, @options)
|
|
119
|
-
target_files =
|
|
120
|
-
|
|
121
|
-
|
|
233
|
+
target_files = scope.select_files(
|
|
234
|
+
target_finder.find(path ? [path] : [], :only_recognized_file_types)
|
|
235
|
+
)
|
|
236
|
+
all_files = target_files.map do |file|
|
|
237
|
+
yield(file, SourceFiles.read(file, @config_store.for_file(file)))
|
|
238
|
+
end
|
|
122
239
|
|
|
123
|
-
|
|
240
|
+
build_result(target_files, all_files)
|
|
124
241
|
end
|
|
125
242
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
raise RuboCop::Error, "No such file or directory: #{file}"
|
|
243
|
+
# Inline code is reported as a single file, so an agent reads one shape
|
|
244
|
+
# whichever way it asked. Files with nothing to report are only counted.
|
|
245
|
+
def build_result(target_files, all_files)
|
|
246
|
+
files = all_files.reject { |f| nothing_to_report?(f) }
|
|
247
|
+
|
|
248
|
+
{ files: files, summary: build_summary(target_files, all_files) }
|
|
133
249
|
end
|
|
134
250
|
|
|
135
|
-
def
|
|
136
|
-
|
|
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}"
|
|
251
|
+
def nothing_to_report?(file)
|
|
252
|
+
file[:offenses].empty? && !file[:corrected] && !file[:errors] && !file[:warnings]
|
|
143
253
|
end
|
|
144
254
|
|
|
145
255
|
# NOTE: It is useful for RuboCop's result summary to be shown in the LLM's responses
|
|
@@ -147,28 +257,16 @@ module RuboCop
|
|
|
147
257
|
# to reason about. Since LLM execution is non-deterministic, it is also sensible to
|
|
148
258
|
# compute the summary deterministically at this stage.
|
|
149
259
|
def build_summary(target_files, files)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
else
|
|
154
|
-
summary[:corrected_file_count] = files.count { |f| f[:corrected] }
|
|
155
|
-
end
|
|
156
|
-
summary
|
|
260
|
+
offense_count = files.sum { |f| f[:offenses].size }
|
|
261
|
+
{ target_file_count: target_files.count, offense_count: offense_count }
|
|
262
|
+
.merge(@json_formatter.problem_counts(files))
|
|
157
263
|
end
|
|
158
264
|
|
|
159
265
|
# rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists
|
|
160
266
|
def build_tool(
|
|
161
267
|
name:, description:,
|
|
162
|
-
title:, destructive_hint:, idempotent_hint:, read_only_hint:,
|
|
268
|
+
title:, destructive_hint:, idempotent_hint:, read_only_hint:, properties:, required: nil
|
|
163
269
|
)
|
|
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
270
|
::MCP::Tool.define(
|
|
173
271
|
name: name,
|
|
174
272
|
description: description,
|
|
@@ -176,8 +274,11 @@ module RuboCop
|
|
|
176
274
|
properties: {
|
|
177
275
|
path: { type: 'string' },
|
|
178
276
|
source_code: { type: 'string' }
|
|
179
|
-
}.merge(
|
|
180
|
-
required: required
|
|
277
|
+
}.merge(properties, Scope::PROPERTIES),
|
|
278
|
+
required: required,
|
|
279
|
+
# The gem checks arguments against the schema, so one the tool
|
|
280
|
+
# doesn't take is refused by name rather than failing the call.
|
|
281
|
+
additionalProperties: false
|
|
181
282
|
}.compact,
|
|
182
283
|
annotations: {
|
|
183
284
|
title: title,
|
|
@@ -186,11 +287,18 @@ module RuboCop
|
|
|
186
287
|
open_world_hint: false,
|
|
187
288
|
read_only_hint: read_only_hint
|
|
188
289
|
}
|
|
189
|
-
) do |
|
|
190
|
-
|
|
290
|
+
) do |**arguments|
|
|
291
|
+
# Taking any keywords makes the gem pass a `server_context` as well.
|
|
292
|
+
# Each tool's own block names the arguments it accepts.
|
|
293
|
+
arguments.delete(:server_context)
|
|
294
|
+
selection = Scope::PROPERTIES.keys.to_h { |key| [key, arguments.delete(key)] }
|
|
295
|
+
scope = Scope.new(inline: !arguments[:source_code].nil?, **selection)
|
|
296
|
+
result = yield(**arguments, scope: scope)
|
|
191
297
|
|
|
192
298
|
::MCP::Tool::Response.new([{ type: 'text', text: result }])
|
|
193
|
-
rescue RuboCop::Error => e
|
|
299
|
+
rescue RuboCop::Error, IncorrectCopNameError => e
|
|
300
|
+
# The runner raises `IncorrectCopNameError` for a name in `only` or
|
|
301
|
+
# `except` that it does not know, with suggestions.
|
|
194
302
|
::MCP::Tool::Response.new([{ type: 'text', text: e.message }], error: true)
|
|
195
303
|
end
|
|
196
304
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module MCP
|
|
5
|
+
# Reads and writes the files the tools work on. A file that can't be read
|
|
6
|
+
# or written fails the request with an error the agent can act on.
|
|
7
|
+
# @api private
|
|
8
|
+
module SourceFiles
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def read(file, config)
|
|
12
|
+
RuboCop::ProcessedSource.from_file(
|
|
13
|
+
file, config.target_ruby_version, parser_engine: config.parser_engine
|
|
14
|
+
).raw_source
|
|
15
|
+
rescue Errno::ENOENT
|
|
16
|
+
raise RuboCop::Error, "No such file or directory: #{file}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Writing what a file already holds would only bump its modification time.
|
|
20
|
+
def write(file, content)
|
|
21
|
+
return if File.file?(file) && File.binread(file) == content.b
|
|
22
|
+
|
|
23
|
+
Util.replace_file_contents(file, content)
|
|
24
|
+
rescue Errno::EACCES
|
|
25
|
+
raise RuboCop::Error, "Permission denied: #{file}"
|
|
26
|
+
rescue Errno::EPERM
|
|
27
|
+
raise RuboCop::Error, "Operation not permitted: #{file}"
|
|
28
|
+
rescue Errno::ENOSPC
|
|
29
|
+
raise RuboCop::Error, "No space left on device: #{file}"
|
|
30
|
+
rescue Errno::EROFS
|
|
31
|
+
raise RuboCop::Error, "Read-only file system: #{file}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
# Caps how many offenses each cop gets to report.
|
|
5
|
+
#
|
|
6
|
+
# Counted across a whole run rather than per file, since a cop that fires a
|
|
7
|
+
# handful of times in every file is exactly the one worth capping. Offenses
|
|
8
|
+
# have to be offered in a stable order (files in the order they are
|
|
9
|
+
# reported) for the same ones to survive every time.
|
|
10
|
+
# @api private
|
|
11
|
+
class OffenseLimit
|
|
12
|
+
attr_reader :max
|
|
13
|
+
|
|
14
|
+
def initialize(max)
|
|
15
|
+
@max = max
|
|
16
|
+
@offered = Hash.new(0)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Array<Cop::Offense>] the offenses still within their cop's limit
|
|
20
|
+
def filter(offenses)
|
|
21
|
+
offenses.select { |offense| (@offered[offense.cop_name] += 1) <= @max }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Hash{String => Integer}] how many offenses each cop had left out,
|
|
25
|
+
# sorted by cop name
|
|
26
|
+
def elided_per_cop
|
|
27
|
+
@offered.filter_map { |cop, count| [cop, count - @max] if count > @max }.sort.to_h
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/rubocop/options.rb
CHANGED
|
@@ -17,7 +17,7 @@ module RuboCop
|
|
|
17
17
|
'cops are enabled (via eg. Include/Exclude), and so that certain cops ' \
|
|
18
18
|
'like Naming/FileName can be checked.'
|
|
19
19
|
EXITING_OPTIONS = %i[
|
|
20
|
-
version verbose_version show_cops list_enabled_cops_for show_docs_url lsp mcp
|
|
20
|
+
version verbose_version show_cops list_enabled_cops_for show_docs_url explain lsp mcp
|
|
21
21
|
].freeze
|
|
22
22
|
DEFAULT_MAXIMUM_EXCLUSION_ITEMS = 15
|
|
23
23
|
|
|
@@ -33,6 +33,7 @@ module RuboCop
|
|
|
33
33
|
|
|
34
34
|
define_options.parse!(args)
|
|
35
35
|
|
|
36
|
+
return_path_given_to_changed(args)
|
|
36
37
|
imply_autocorrect_for_diff
|
|
37
38
|
|
|
38
39
|
@validator.validate_compatibility
|
|
@@ -53,6 +54,12 @@ module RuboCop
|
|
|
53
54
|
|
|
54
55
|
private
|
|
55
56
|
|
|
57
|
+
# `--changed lib` hands the path to `--changed` as its optional argument,
|
|
58
|
+
# so it goes back among the paths to inspect.
|
|
59
|
+
def return_path_given_to_changed(args)
|
|
60
|
+
args.unshift(@path_given_to_changed) if @path_given_to_changed
|
|
61
|
+
end
|
|
62
|
+
|
|
56
63
|
# `--diff` is a dry run of autocorrection, so it turns autocorrection on
|
|
57
64
|
# unless the user already picked a mode with `-a`, `-A` or `-x`.
|
|
58
65
|
def imply_autocorrect_for_diff
|
|
@@ -111,8 +118,11 @@ module RuboCop
|
|
|
111
118
|
option(opts, '--ignore-unrecognized-cops')
|
|
112
119
|
option(opts, '--force-default-config')
|
|
113
120
|
option(opts, '--changed [REVISION]') do |revision|
|
|
121
|
+
if @validator.path_given_to_changed?(revision)
|
|
122
|
+
@path_given_to_changed = revision
|
|
123
|
+
revision = nil
|
|
124
|
+
end
|
|
114
125
|
@options[:changed] = revision || ChangedFiles::DEFAULT_REVISION
|
|
115
|
-
@validator.validate_changed_revision(revision)
|
|
116
126
|
end
|
|
117
127
|
option(opts, '-s', '--stdin FILE')
|
|
118
128
|
option(opts, '--editor-mode')
|
|
@@ -148,6 +158,9 @@ module RuboCop
|
|
|
148
158
|
option(opts, '--display-only-correctable')
|
|
149
159
|
option(opts, '--display-only-safe-correctable')
|
|
150
160
|
option(opts, '--display-suppressed')
|
|
161
|
+
option(opts, '--max-offenses-per-cop COUNT') do
|
|
162
|
+
@validator.validate_max_offenses_per_cop_option
|
|
163
|
+
end
|
|
151
164
|
end
|
|
152
165
|
end
|
|
153
166
|
|
|
@@ -267,6 +280,9 @@ module RuboCop
|
|
|
267
280
|
option(opts, '--show-docs-url [COP1,COP2,...]') do |list|
|
|
268
281
|
@options[:show_docs_url] = list.nil? ? [] : list.split(',')
|
|
269
282
|
end
|
|
283
|
+
option(opts, '--explain COP1,COP2,...') do |list|
|
|
284
|
+
@options[:explain] = list.split(',')
|
|
285
|
+
end
|
|
270
286
|
end
|
|
271
287
|
end
|
|
272
288
|
|
|
@@ -509,7 +525,7 @@ module RuboCop
|
|
|
509
525
|
end
|
|
510
526
|
|
|
511
527
|
def disable_parallel_when_invalid_option_combo
|
|
512
|
-
return unless @options
|
|
528
|
+
return unless @options[:parallel]
|
|
513
529
|
|
|
514
530
|
invalid_flags = invalid_arguments_for_parallel
|
|
515
531
|
|
|
@@ -517,7 +533,9 @@ module RuboCop
|
|
|
517
533
|
|
|
518
534
|
@options.delete(:parallel)
|
|
519
535
|
|
|
520
|
-
|
|
536
|
+
# On stderr, so that it can't end up in the middle of a formatter's
|
|
537
|
+
# output, such as `--format json`.
|
|
538
|
+
warn '-P/--parallel is being ignored because ' \
|
|
521
539
|
"it is not compatible with #{invalid_flags.join(', ')}."
|
|
522
540
|
end
|
|
523
541
|
|
|
@@ -546,14 +564,22 @@ module RuboCop
|
|
|
546
564
|
end
|
|
547
565
|
|
|
548
566
|
# OptionParser hands `--changed lib/` the path as the revision, which is a
|
|
549
|
-
# natural thing to type, so
|
|
550
|
-
|
|
551
|
-
|
|
567
|
+
# natural thing to type, so an argument that names an existing path is
|
|
568
|
+
# taken as one. When it also names a revision there is no telling which
|
|
569
|
+
# was meant, and git itself refuses such an argument as ambiguous.
|
|
570
|
+
def path_given_to_changed?(revision)
|
|
571
|
+
return false unless revision && File.exist?(revision)
|
|
572
|
+
return true unless ChangedFiles.revision?(revision)
|
|
552
573
|
|
|
553
574
|
raise OptionArgumentError,
|
|
554
|
-
"--changed
|
|
555
|
-
"
|
|
556
|
-
|
|
575
|
+
"--changed got `#{revision}`, which is both a path and a git revision. Write " \
|
|
576
|
+
"`./#{revision}` for the path, or `--changed=#{revision}^0` for the revision."
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
def validate_max_offenses_per_cop_option
|
|
580
|
+
return if /^[1-9]\d*$/.match?(@options[:max_offenses_per_cop])
|
|
581
|
+
|
|
582
|
+
raise OptionParser::InvalidArgument, '--max-offenses-per-cop must be a positive integer'
|
|
557
583
|
end
|
|
558
584
|
|
|
559
585
|
def validate_exclude_limit_option
|
|
@@ -611,6 +637,11 @@ module RuboCop
|
|
|
611
637
|
'when running --auto-gen-config, except if the',
|
|
612
638
|
'number of files with offenses is bigger than',
|
|
613
639
|
'exclude-limit. Default is false.'],
|
|
640
|
+
max_offenses_per_cop: ['Report at most COUNT offenses per cop across the',
|
|
641
|
+
'whole run, so a single noisy cop cannot bury the',
|
|
642
|
+
'rest. Offenses beyond the limit are not reported',
|
|
643
|
+
'and do not count towards the totals, the same way',
|
|
644
|
+
'the other display filters work.'],
|
|
614
645
|
exclude_limit: ['Set the limit for how many files to explicitly exclude.',
|
|
615
646
|
'If there are more files than the limit, the cop will',
|
|
616
647
|
"be disabled instead. Default is #{MAX_EXCL}."],
|
|
@@ -674,6 +705,10 @@ module RuboCop
|
|
|
674
705
|
'default, and their configurations for the',
|
|
675
706
|
'current directory.',
|
|
676
707
|
'You can use `*` as a wildcard.'],
|
|
708
|
+
explain: ['Explain what the given cops do: their',
|
|
709
|
+
'description, configuration, whether they',
|
|
710
|
+
'correct, and the examples from their own',
|
|
711
|
+
'documentation.'],
|
|
677
712
|
show_docs_url: ['Display url to documentation for the given',
|
|
678
713
|
'cops, or base url by default.'],
|
|
679
714
|
fail_fast: ['Inspect files in order of modification',
|