123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::CopExplanation Private

Relationships & Source Files
Inherits: Object
Defined in: lib/rubocop/cop/cop_explanation.rb

Overview

Explains what a single cop does: its description, how it is configured, whether it corrects, and the examples from its own documentation.

Rendered as plain text, which is what --explain prints and what the MCP server hands to an agent.

Constant Summary

  • METADATA_KEYS =

    Keys that describe the cop to RuboCop itself rather than something a user would set, so they are reported in their own section or not at all.

    # File 'lib/rubocop/cop/cop_explanation.rb', line 14
    %w[
      Description StyleGuide Reference References Enabled Safe SafeAutoCorrect
      AutoCorrect VersionAdded VersionChanged Include Exclude Details Preview
    ].freeze

Class Method Summary

Instance Method Summary

Constructor Details

.new(cop_class, config) ⇒ CopExplanation

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 19

def initialize(cop_class, config)
  @cop_class = cop_class
  @config = config
  @cop_config = config.for_cop(cop_class.cop_name)
end

Instance Method Details

#autocorrect_description (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 88

def autocorrect_description
  return 'not supported' unless @cop_class.support_autocorrect?

  # Asks the cop, so `AutoCorrect` is read the way the cop reads it.
  cop = @cop_class.new(@config)
  return 'disabled' unless cop.always_autocorrect? || cop.contextual_autocorrect?

  description = cop.safe_autocorrect? ? 'safe, applied by -a' : 'unsafe, applied by -A only'
  # Contextual corrections wait until the code is finished, which an
  # editor or an agent mid-edit cannot promise, so they have to ask.
  description += ', but through LSP or MCP only on request' if cop.contextual_autocorrect?
  description
end

#configuration_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 109

def configuration_lines
  (@cop_config.keys - METADATA_KEYS).sort.map do |key|
    "#{key}: #{format_value(@cop_config[key])}"
  end
end

#description_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 51

def description_lines
  Array(@cop_config['Description']).flat_map { |line| line.split("\n") }
end

#documentation_sections(description) (private)

The comment above the class opens by restating the description, which is already printed above, so that opening is dropped.

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 127

def documentation_sections(description)
  CopDocumentation.new(@cop_class).sections.filter_map do |title, body|
    body -= description if title == 'Details'
    body = strip_blank(body)
    [title, body] unless body.empty?
  end
end

#format_value(value) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 115

def format_value(value)
  value.is_a?(Array) ? value.join(', ') : value.to_s
end

#lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 31

def lines
  description = description_lines

  [
    @cop_class.cop_name,
    *section(nil, description),
    *section('Properties', property_lines),
    *documentation_sections(description).flat_map { |title, body| section(title, body) },
    *section('Configuration', configuration_lines),
    *section('References', reference_lines)
  ]
end

#patterns_line(label, patterns) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 74

def patterns_line(label, patterns)
  patterns = Array(patterns).compact
  return [] if patterns.empty?

  ["#{label}#{patterns.map { |pattern| readable_pattern(pattern) }.join(', ')}"]
end

#property_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 55

def property_lines
  [
    "Enabled:     #{@cop_config.fetch('Enabled', true)}",
    "Safe:        #{@cop_config.fetch('Safe', true)}",
    "Autocorrect: #{autocorrect_description}",
    *version_lines,
    *scope_lines
  ]
end

#readable_pattern(pattern) (private)

Exclude patterns are absolutized against the config that set them, which makes for a wall of identical prefixes. A user config can also hold a regexp rather than a glob.

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 84

def readable_pattern(pattern)
  pattern.is_a?(String) ? PathUtil.smart_path(pattern) : pattern.inspect
end

#reference_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 119

def reference_lines
  annotator = MessageAnnotator.new(@config, @cop_class.cop_name, @cop_config, {})

  [*annotator.urls, Documentation.url_for(@cop_class, @config)].compact
end

#scope_lines (private)

Which files the cop looks at. A cop that only runs on Gemfiles explains an absent offense better than anything else here does.

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 67

def scope_lines
  [
    *patterns_line('Applies to:  ', @cop_config['Include']),
    *patterns_line('Excludes:    ', @cop_config['Exclude'])
  ]
end

#section(title, body) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 44

def section(title, body)
  return [] if body.empty?

  heading = title ? [title, ''] : []
  ['', *heading, *body.map { |line| line.empty? ? '' : "  #{line}" }]
end

#strip_blank(lines) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 135

def strip_blank(lines)
  lines.drop_while(&:empty?).reverse.drop_while(&:empty?).reverse
end

#to_s

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 25

def to_s
  lines.join("\n")
end

#version_lines (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/cop_explanation.rb', line 102

def version_lines
  lines = []
  lines << "Added:       #{@cop_config['VersionAdded']}" if @cop_config['VersionAdded']
  lines << "Changed:     #{@cop_config['VersionChanged']}" if @cop_config['VersionChanged']
  lines
end