rubocop 1.91.0 → 1.92.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/README.md +1 -1
- data/config/default.yml +3 -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_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_validator.rb +11 -0
- data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
- 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 +22 -7
- data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +23 -2
- 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/block_alignment.rb +27 -0
- data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
- data/lib/rubocop/cop/layout/class_structure.rb +9 -1
- data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
- data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +14 -5
- data/lib/rubocop/cop/layout/empty_lines.rb +5 -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 +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
- data/lib/rubocop/cop/layout/end_alignment.rb +16 -10
- data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +5 -3
- 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 +2 -0
- data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
- 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 +52 -18
- 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 +7 -0
- data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +14 -0
- 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 +15 -2
- 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_before_block_braces.rb +4 -1
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
- 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/empty_conditional_body.rb +3 -1
- 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/number_conversion.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
- 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/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 +17 -14
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +5 -1
- data/lib/rubocop/cop/mixin/code_length.rb +10 -2
- data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
- data/lib/rubocop/cop/mixin/endless_method_body_help.rb +69 -0
- data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +15 -1
- 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 +22 -2
- 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/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 +7 -0
- 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 +4 -0
- data/lib/rubocop/cop/style/arguments_forwarding.rb +4 -1
- 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 +29 -3
- data/lib/rubocop/cop/style/case_like_if.rb +6 -3
- data/lib/rubocop/cop/style/class_and_module_children.rb +54 -16
- data/lib/rubocop/cop/style/class_methods_definitions.rb +8 -0
- data/lib/rubocop/cop/style/combinable_loops.rb +85 -11
- data/lib/rubocop/cop/style/concat_array_literals.rb +46 -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/endless_method.rb +67 -6
- 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 +22 -2
- data/lib/rubocop/cop/style/for.rb +40 -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 +9 -1
- data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
- data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -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 +19 -20
- 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 +32 -3
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
- data/lib/rubocop/cop/style/lambda.rb +18 -0
- 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 +45 -10
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
- data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
- data/lib/rubocop/cop/style/missing_else.rb +7 -1
- 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 +28 -1
- 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 +21 -5
- data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
- data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
- data/lib/rubocop/cop/style/redundant_begin.rb +20 -3
- 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 +15 -3
- data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
- data/lib/rubocop/cop/style/redundant_freeze.rb +10 -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/regexp_literal.rb +75 -26
- data/lib/rubocop/cop/style/require_order.rb +9 -0
- data/lib/rubocop/cop/style/rescue_modifier.rb +5 -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/single_line_methods.rb +16 -4
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
- data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
- data/lib/rubocop/cop/style/super_arguments.rb +3 -0
- data/lib/rubocop/cop/style/symbol_proc.rb +23 -5
- 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/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 +12 -5
- data/lib/rubocop/remote_config.rb +4 -1
- data/lib/rubocop/result_cache.rb +20 -13
- data/lib/rubocop/runner.rb +109 -41
- data/lib/rubocop/server/cache.rb +2 -2
- data/lib/rubocop/util.rb +69 -0
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +5 -0
- metadata +16 -5
|
@@ -13,19 +13,29 @@ module RuboCop
|
|
|
13
13
|
def initialize(output, options = {})
|
|
14
14
|
super
|
|
15
15
|
@output_hash = { metadata: metadata_hash, files: [], summary: { offense_count: 0 } }
|
|
16
|
+
@problems = {}
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def started(target_files)
|
|
19
20
|
output_hash[:summary][:target_file_count] = target_files.count
|
|
20
21
|
end
|
|
21
22
|
|
|
23
|
+
def file_problems(file, errors, warnings)
|
|
24
|
+
@problems[file] = hash_for_problems(file, errors, warnings)
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
def file_finished(file, offenses)
|
|
23
|
-
|
|
28
|
+
entry = hash_for_file(file, offenses)
|
|
29
|
+
problems = @problems.delete(file)
|
|
30
|
+
entry.merge!(problems) if problems
|
|
31
|
+
|
|
32
|
+
output_hash[:files] << entry
|
|
24
33
|
output_hash[:summary][:offense_count] += offenses.count
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
def finished(inspected_files)
|
|
28
37
|
output_hash[:summary][:inspected_file_count] = inspected_files.count
|
|
38
|
+
output_hash[:summary].merge!(problem_counts(output_hash[:files]))
|
|
29
39
|
output.write output_hash.to_json
|
|
30
40
|
end
|
|
31
41
|
|
|
@@ -46,6 +56,32 @@ module RuboCop
|
|
|
46
56
|
}
|
|
47
57
|
end
|
|
48
58
|
|
|
59
|
+
# A cop that crashes fails once per node it visits, so messages that
|
|
60
|
+
# differ only in their line and column are listed once, at the first place
|
|
61
|
+
# they happened. They name the file the way its entry does. The keys are
|
|
62
|
+
# absent when there's nothing to report, which keeps them additive for
|
|
63
|
+
# existing consumers.
|
|
64
|
+
def hash_for_problems(file, errors, warnings)
|
|
65
|
+
return {} if errors.empty? && warnings.empty?
|
|
66
|
+
|
|
67
|
+
path = smart_path(file)
|
|
68
|
+
location = /#{Regexp.escape(File.expand_path(file))}((?::\d+)*)/
|
|
69
|
+
|
|
70
|
+
{ errors: errors, warnings: warnings }.filter_map do |key, messages|
|
|
71
|
+
next if messages.empty?
|
|
72
|
+
|
|
73
|
+
messages = messages.uniq { |message| message.gsub(location, '') }
|
|
74
|
+
[key, messages.map { |message| message.gsub(location) { path + Regexp.last_match(1) } }]
|
|
75
|
+
end.to_h
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def problem_counts(files)
|
|
79
|
+
{
|
|
80
|
+
error_count: files.sum { |f| f[:errors]&.size.to_i },
|
|
81
|
+
warning_count: files.sum { |f| f[:warnings]&.size.to_i }
|
|
82
|
+
}.reject { |_, count| count.zero? }
|
|
83
|
+
end
|
|
84
|
+
|
|
49
85
|
def hash_for_offense(offense)
|
|
50
86
|
hash = {
|
|
51
87
|
severity: offense.severity.name,
|
|
@@ -63,9 +99,43 @@ module RuboCop
|
|
|
63
99
|
hash[:justification] = offense.justification
|
|
64
100
|
end
|
|
65
101
|
|
|
102
|
+
# The edits autocorrection would make, so a consumer can apply or review
|
|
103
|
+
# them without running autocorrection itself. Additive, and absent when
|
|
104
|
+
# the offense has no correction.
|
|
105
|
+
hash[:correction] = correction_for(offense) unless offense.corrections.empty?
|
|
106
|
+
|
|
66
107
|
hash
|
|
67
108
|
end
|
|
68
109
|
|
|
110
|
+
def correction_for(offense)
|
|
111
|
+
{ safe: offense.correction_safe, edits: edits_for(offense) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def edits_for(offense)
|
|
115
|
+
buffer = offense.location.source_buffer
|
|
116
|
+
|
|
117
|
+
offense.corrections.map do |correction|
|
|
118
|
+
# Built as a range so the columns follow the same 1-based convention
|
|
119
|
+
# as `hash_for_location`, end-exclusive column included.
|
|
120
|
+
range = ::Parser::Source::Range.new(buffer, correction.begin_pos, correction.end_pos)
|
|
121
|
+
|
|
122
|
+
hash_for_edit_range(range).merge!(
|
|
123
|
+
begin_pos: correction.begin_pos,
|
|
124
|
+
end_pos: correction.end_pos,
|
|
125
|
+
replacement: correction.replacement
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def hash_for_edit_range(range)
|
|
131
|
+
{
|
|
132
|
+
start_line: range.line,
|
|
133
|
+
start_column: range.column + 1,
|
|
134
|
+
last_line: range.last_line,
|
|
135
|
+
last_column: range.last_column.zero? ? 1 : range.last_column
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
69
139
|
def hash_for_location(offense)
|
|
70
140
|
{
|
|
71
141
|
start_line: offense.line,
|
|
@@ -14,6 +14,8 @@ module RuboCop
|
|
|
14
14
|
# --
|
|
15
15
|
# 29 Total in 5 files
|
|
16
16
|
class OffenseCountFormatter < BaseFormatter
|
|
17
|
+
include TextUtil
|
|
18
|
+
|
|
17
19
|
attr_reader :offense_counts
|
|
18
20
|
|
|
19
21
|
def started(target_files)
|
|
@@ -64,7 +66,7 @@ module RuboCop
|
|
|
64
66
|
output.puts "#{count.to_s.ljust(column_width)}#{cop_information(cop_name)}"
|
|
65
67
|
end
|
|
66
68
|
output.puts '--'
|
|
67
|
-
output.puts "#{total_count} Total in #{offending_files_count}
|
|
69
|
+
output.puts "#{total_count} Total in #{pluralize(offending_files_count, 'file')}"
|
|
68
70
|
|
|
69
71
|
output.puts
|
|
70
72
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
3
5
|
module RuboCop
|
|
4
6
|
module Formatter
|
|
5
7
|
# This formatter prints a PACDOT per every file to be analyzed.
|
|
@@ -50,7 +52,7 @@ module RuboCop
|
|
|
50
52
|
|
|
51
53
|
def cols
|
|
52
54
|
@cols ||= begin
|
|
53
|
-
_height, width =
|
|
55
|
+
_height, width = output.winsize if output.tty?
|
|
54
56
|
width.nil? || width.zero? ? FALLBACK_TERMINAL_WIDTH : width
|
|
55
57
|
end
|
|
56
58
|
end
|
|
@@ -38,8 +38,9 @@ module RuboCop
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def file_finished(file, offenses)
|
|
41
|
+
line_hash_counts = Hash.new(0)
|
|
41
42
|
offenses.each do |offense|
|
|
42
|
-
@results << result_for(file, offense)
|
|
43
|
+
@results << result_for(file, offense, line_hash_counts)
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
|
|
@@ -67,14 +68,14 @@ module RuboCop
|
|
|
67
68
|
}
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
def result_for(file, offense)
|
|
71
|
+
def result_for(file, offense, line_hash_counts)
|
|
71
72
|
result = {
|
|
72
73
|
ruleId: offense.cop_name,
|
|
73
74
|
ruleIndex: rule_index_for(file, offense),
|
|
74
75
|
level: SEVERITY_LEVELS.fetch(offense.severity.name, 'warning'),
|
|
75
76
|
message: { text: offense.message },
|
|
76
77
|
locations: [location_for(file, offense)],
|
|
77
|
-
partialFingerprints: fingerprints_for(offense)
|
|
78
|
+
partialFingerprints: fingerprints_for(offense, line_hash_counts)
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
# Suppressed offenses are only reported under `--display-suppressed`.
|
|
@@ -110,9 +111,13 @@ module RuboCop
|
|
|
110
111
|
|
|
111
112
|
# GitHub uses these to match an alert across runs. Without them it falls
|
|
112
113
|
# back to the file path, which makes alerts churn whenever a file moves.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
# Repeats of the same hash within a file get a `:N` suffix, like GitHub's
|
|
115
|
+
# own fingerprints, so two offenses on one line don't merge into one alert.
|
|
116
|
+
# The first occurrence keeps the bare hash to match earlier reports.
|
|
117
|
+
def fingerprints_for(offense, line_hash_counts)
|
|
118
|
+
line_hash = Digest::SHA256.hexdigest("#{offense.cop_name}#{offense.location.source_line}")
|
|
119
|
+
count = line_hash_counts[line_hash] += 1
|
|
120
|
+
{ primaryLocationLineHash: count == 1 ? line_hash : "#{line_hash}:#{count}" }
|
|
116
121
|
end
|
|
117
122
|
|
|
118
123
|
def suppression_for(offense)
|
|
@@ -158,7 +163,7 @@ module RuboCop
|
|
|
158
163
|
|
|
159
164
|
def default_level_for(file, offense, cop_class)
|
|
160
165
|
severity = cop_config(file, offense)['Severity'] ||
|
|
161
|
-
(cop_class&.
|
|
166
|
+
Cop::Base::DEPARTMENT_SEVERITIES.fetch(cop_class&.department, :convention)
|
|
162
167
|
|
|
163
168
|
SEVERITY_LEVELS.fetch(severity.to_sym, 'warning')
|
|
164
169
|
end
|
|
@@ -14,6 +14,8 @@ module RuboCop
|
|
|
14
14
|
# --
|
|
15
15
|
# 29 Total in 2 files
|
|
16
16
|
class WorstOffendersFormatter < BaseFormatter
|
|
17
|
+
include TextUtil
|
|
18
|
+
|
|
17
19
|
attr_reader :offense_counts
|
|
18
20
|
|
|
19
21
|
def started(target_files)
|
|
@@ -46,7 +48,7 @@ module RuboCop
|
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
output.puts '--'
|
|
49
|
-
output.puts "#{total_count} Total in #{file_count}
|
|
51
|
+
output.puts "#{total_count} Total in #{pluralize(file_count, 'file')}"
|
|
50
52
|
|
|
51
53
|
output.puts
|
|
52
54
|
end
|
data/lib/rubocop/lsp/runtime.rb
CHANGED
|
@@ -17,27 +17,31 @@ module RuboCop
|
|
|
17
17
|
# Runtime for Language Server Protocol of RuboCop.
|
|
18
18
|
# @api private
|
|
19
19
|
class Runtime
|
|
20
|
-
attr_writer :safe_autocorrect, :lint_mode, :layout_mode
|
|
20
|
+
attr_writer :safe_autocorrect, :lint_mode, :layout_mode, :raise_cop_error
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
# Without `lsp_mode`, offenses keep the locations and messages the
|
|
23
|
+
# command line gives them, rather than the ones meant for an editor.
|
|
24
|
+
def initialize(config_store, lsp_mode: true)
|
|
25
|
+
RuboCop::LSP.enable if lsp_mode
|
|
24
26
|
|
|
25
27
|
@runner = RuboCop::Lsp::StdinRunner.new(config_store)
|
|
26
28
|
|
|
27
29
|
@safe_autocorrect = true
|
|
28
30
|
@lint_mode = false
|
|
29
31
|
@layout_mode = false
|
|
32
|
+
@raise_cop_error = true
|
|
30
33
|
end
|
|
31
34
|
|
|
32
|
-
def format(path, text, command:, prism_result: nil)
|
|
35
|
+
def format(path, text, command:, prism_result: nil, options: {})
|
|
33
36
|
safe_autocorrect = if command
|
|
34
37
|
command == 'rubocop.formatAutocorrects'
|
|
35
38
|
else
|
|
36
39
|
@safe_autocorrect
|
|
37
40
|
end
|
|
38
41
|
|
|
39
|
-
formatting_options = {
|
|
40
|
-
|
|
42
|
+
formatting_options = {
|
|
43
|
+
autocorrect: true, safe_autocorrect: safe_autocorrect, raise_cop_error: @raise_cop_error
|
|
44
|
+
}.merge(call_options(options))
|
|
41
45
|
|
|
42
46
|
@runner.run(path, text, formatting_options, prism_result: prism_result)
|
|
43
47
|
@runner.formatted_source
|
|
@@ -48,17 +52,39 @@ module RuboCop
|
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
def offenses(path, text, document_encoding = nil, prism_result: nil)
|
|
51
|
-
|
|
52
|
-
diagnostic_options[:only] = config_only_options if @lint_mode || @layout_mode
|
|
53
|
-
|
|
54
|
-
@runner.run(path, text, diagnostic_options, prism_result: prism_result)
|
|
55
|
+
found = raw_offenses(path, text, prism_result: prism_result)
|
|
55
56
|
processed_source = @runner.processed_source
|
|
56
57
|
config = @runner.config_for_working_directory
|
|
57
|
-
|
|
58
|
+
found.map do |offense|
|
|
58
59
|
build_diagnostic(offense, path, document_encoding, processed_source, config)
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
|
|
63
|
+
# The offenses before they are converted to LSP diagnostics, for callers
|
|
64
|
+
# that do not speak LSP.
|
|
65
|
+
def raw_offenses(path, text, prism_result: nil, options: {})
|
|
66
|
+
diagnostic_options = { raise_cop_error: @raise_cop_error }.merge(call_options(options))
|
|
67
|
+
|
|
68
|
+
@runner.run(path, text, diagnostic_options, prism_result: prism_result)
|
|
69
|
+
@runner.offenses
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The offenses the last `format` found but didn't correct, located in the
|
|
73
|
+
# source it returned.
|
|
74
|
+
def uncorrected_offenses
|
|
75
|
+
@runner.offenses.reject(&:corrected?)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# What went wrong in the last run. Cop errors only end up here when
|
|
79
|
+
# `raise_cop_error` is off; otherwise they are raised.
|
|
80
|
+
def errors
|
|
81
|
+
@runner.errors
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def warnings
|
|
85
|
+
@runner.warnings
|
|
86
|
+
end
|
|
87
|
+
|
|
62
88
|
private
|
|
63
89
|
|
|
64
90
|
def build_diagnostic(offense, path, document_encoding, processed_source, config)
|
|
@@ -71,6 +97,16 @@ module RuboCop
|
|
|
71
97
|
).to_lsp_diagnostic(config)
|
|
72
98
|
end
|
|
73
99
|
|
|
100
|
+
# What a caller adds for one call, such as `only` and `except` to select
|
|
101
|
+
# the cops, or `editing` to hold back the corrections of cops with
|
|
102
|
+
# `AutoCorrect: contextual`, as LSP mode does. Without `only`, the cops
|
|
103
|
+
# are the departments a lint or layout mode limits the server to.
|
|
104
|
+
def call_options(options)
|
|
105
|
+
return options if options[:only] || !(@lint_mode || @layout_mode)
|
|
106
|
+
|
|
107
|
+
options.merge(only: config_only_options)
|
|
108
|
+
end
|
|
109
|
+
|
|
74
110
|
def config_only_options
|
|
75
111
|
only_options = []
|
|
76
112
|
only_options << 'Lint' if @lint_mode
|
|
@@ -23,7 +23,6 @@ module RuboCop
|
|
|
23
23
|
stderr: true,
|
|
24
24
|
force_exclusion: true,
|
|
25
25
|
formatters: ['RuboCop::Formatter::BaseFormatter'],
|
|
26
|
-
raise_cop_error: true,
|
|
27
26
|
todo_file: nil,
|
|
28
27
|
todo_ignore_files: []
|
|
29
28
|
}.freeze
|
|
@@ -41,6 +40,13 @@ module RuboCop
|
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
def run(path, contents, options, prism_result: nil)
|
|
43
|
+
# The cops mobilized for a configuration are cached, which only holds while
|
|
44
|
+
# every run asks for the same ones.
|
|
45
|
+
selection = options.values_at(:only, :except)
|
|
46
|
+
unless selection == @cop_selection
|
|
47
|
+
forget_mobilized_cops
|
|
48
|
+
@cop_selection = selection
|
|
49
|
+
end
|
|
44
50
|
@options = options.merge(DEFAULT_RUBOCOP_OPTIONS)
|
|
45
51
|
@options[:stdin] = contents
|
|
46
52
|
|
|
@@ -86,10 +92,10 @@ module RuboCop
|
|
|
86
92
|
@offenses = offenses
|
|
87
93
|
end
|
|
88
94
|
|
|
89
|
-
def do_inspection_loop(file)
|
|
90
|
-
|
|
91
|
-
@processed_source =
|
|
92
|
-
[
|
|
95
|
+
def do_inspection_loop(file, source = nil)
|
|
96
|
+
processed_source, offenses = super
|
|
97
|
+
@processed_source = processed_source
|
|
98
|
+
[processed_source, offenses]
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
end
|
data/lib/rubocop/lsp.rb
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module MCP
|
|
5
|
+
# What an autocorrection request asks for, shared by every file it corrects.
|
|
6
|
+
# The keywords are the tool's arguments, so an unknown one is refused.
|
|
7
|
+
# @api private
|
|
8
|
+
class AutocorrectionRequest
|
|
9
|
+
PROPERTIES = {
|
|
10
|
+
safety: { type: 'boolean' },
|
|
11
|
+
contextual: { type: 'boolean' },
|
|
12
|
+
dry_run: { type: 'boolean' },
|
|
13
|
+
max_offenses_per_cop: { type: 'integer', minimum: 1 }
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# The offenses left over come back after the files are written, so they're
|
|
17
|
+
# capped unless the agent asks otherwise: a result too big for the client
|
|
18
|
+
# would hide what was done.
|
|
19
|
+
DEFAULT_MAX_OFFENSES_PER_COP = 5
|
|
20
|
+
|
|
21
|
+
attr_reader :scope, :limit
|
|
22
|
+
|
|
23
|
+
def initialize(scope, safety: true, contextual: false, dry_run: false,
|
|
24
|
+
max_offenses_per_cop: DEFAULT_MAX_OFFENSES_PER_COP)
|
|
25
|
+
@scope = scope
|
|
26
|
+
@safety = safety
|
|
27
|
+
@contextual = contextual
|
|
28
|
+
@dry_run = dry_run
|
|
29
|
+
@limit = OffenseLimit.new(max_offenses_per_cop)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def dry_run?
|
|
33
|
+
@dry_run
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The options `LSP::Runtime#format` takes to apply the corrections
|
|
37
|
+
# `safety` and `contextual` allow, from the cops the scope selects.
|
|
38
|
+
def format_options
|
|
39
|
+
command = @safety ? 'rubocop.formatAutocorrects' : 'rubocop.formatAutocorrectsAll'
|
|
40
|
+
{ command: command, options: runtime_options }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The options the LSP runtime takes for each call the request makes.
|
|
44
|
+
# Cops with `AutoCorrect: contextual` hold back corrections that get in
|
|
45
|
+
# the way of code still being written, such as removing a variable that
|
|
46
|
+
# is about to be used, so an agent that is done editing has to ask for them.
|
|
47
|
+
def runtime_options
|
|
48
|
+
{ **@scope.cop_options, editing: !@contextual }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module MCP
|
|
5
|
+
# The `rubocop_explain` tool: what cops do, as `--explain` prints it.
|
|
6
|
+
# @api private
|
|
7
|
+
class ExplainTool
|
|
8
|
+
include RuboCop::CLI::Command::CopNames
|
|
9
|
+
|
|
10
|
+
DESCRIPTION =
|
|
11
|
+
'Explain what cops do: the problem each one targets, bad and good examples, ' \
|
|
12
|
+
'its configuration as this project resolves it, and whether it autocorrects. ' \
|
|
13
|
+
'Takes cop names as offenses report them, such as `Style/StringLiterals`. ' \
|
|
14
|
+
'Pass the `path` of a file to see the configuration that applies to it.'
|
|
15
|
+
INPUT_SCHEMA = {
|
|
16
|
+
properties: {
|
|
17
|
+
cop_names: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
18
|
+
path: { type: 'string' }
|
|
19
|
+
},
|
|
20
|
+
required: ['cop_names'],
|
|
21
|
+
additionalProperties: false
|
|
22
|
+
}.freeze
|
|
23
|
+
ANNOTATIONS = {
|
|
24
|
+
title: "RuboCop's cop explanation", destructive_hint: false, idempotent_hint: true,
|
|
25
|
+
open_world_hint: false, read_only_hint: true
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
def initialize(config_store)
|
|
29
|
+
@config_store = config_store
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The tool's block does not run in this instance, so it reaches
|
|
33
|
+
# `explain` through a method object it closes over.
|
|
34
|
+
def to_tool
|
|
35
|
+
explain = method(:explain)
|
|
36
|
+
|
|
37
|
+
::MCP::Tool.define(
|
|
38
|
+
name: 'rubocop_explain',
|
|
39
|
+
description: DESCRIPTION,
|
|
40
|
+
input_schema: INPUT_SCHEMA,
|
|
41
|
+
annotations: ANNOTATIONS
|
|
42
|
+
) do |cop_names:, path: nil|
|
|
43
|
+
::MCP::Tool::Response.new([{ type: 'text', text: explain.call(cop_names, path) }])
|
|
44
|
+
rescue RuboCop::Error => e
|
|
45
|
+
::MCP::Tool::Response.new([{ type: 'text', text: e.message }], error: true)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Explains the cops it recognizes and then names the ones it does not, as
|
|
52
|
+
# `--explain` does, so a typo does not cost the explanations asked for
|
|
53
|
+
# alongside it. The configuration is loaded first, since that is what
|
|
54
|
+
# registers the cops of any plugins it names.
|
|
55
|
+
def explain(cop_names, path)
|
|
56
|
+
config = path ? @config_store.for_file(path) : @config_store.for_pwd
|
|
57
|
+
explanations = known_cop_classes(cop_names).map do |cop_class|
|
|
58
|
+
Cop::CopExplanation.new(cop_class, config).to_s
|
|
59
|
+
end
|
|
60
|
+
validate_cop_names!(cop_names)
|
|
61
|
+
|
|
62
|
+
explanations.join("\n\n")
|
|
63
|
+
rescue IncorrectCopNameError => e
|
|
64
|
+
raise RuboCop::Error, [*explanations, e.message].join("\n\n")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -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
|