123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Team

Relationships & Source Files
Namespace Children
Classes:
Inherits: Object
Defined in: lib/rubocop/cop/team.rb

Overview

A group of cops, ready to be called on duty to inspect files. Team is responsible for selecting only relevant cops to be sent on duty, as well as insuring that the needed forces are sent along with them.

For performance reasons, Team will first dispatch cops & forces in two groups, first the ones needed for autocorrection (if any), then the rest (unless autocorrections happened).

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(cop_or_classes, config, options = {}) ⇒ Team

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 18

def self.new(cop_or_classes, config, options = {})
  # Support v0 api:
  if cop_or_classes.first.is_a?(Class)
    warn Rainbow(<<~WARNING).yellow, uplevel: 1
      `Team.new` with cop classes is deprecated. Use `Team.mobilize` instead.
    WARNING
    return mobilize(cop_or_classes, config, options)
  end

  super
end

#initialize(cops, config = nil, options = {}) ⇒ Team

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 89

def initialize(cops, config = nil, options = {})
  @cops = cops
  @config = config
  @options = options
  @standby_registry = nil
  @standby_cops = {}
  reset
  @ready = true
  @registry = Registry.new(cops, options.dup)

  validate_config
end

Class Method Details

.forces_for(cops) ⇒ Array<Force>

Returns:

  • (Array<Force>)

    needed for the given cops

[ GitHub ]

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

def self.forces_for(cops)
  needed = Hash.new { |h, k| h[k] = [] }
  cops.each do |cop|
    forces = cop.class.joining_forces
    if forces.is_a?(Array)
      forces.each { |force| needed[force] << cop }
    elsif forces
      needed[forces] << cop
    end
  end

  needed.map { |force_class, joining_cops| force_class.new(joining_cops) }
end

.mobilize(cop_classes, config, options = {}) ⇒ Team

Returns:

  • (Team)

    with cops assembled from the given cop_classes

[ GitHub ]

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

def self.mobilize(cop_classes, config, options = {})
  if cop_classes.is_a?(Registry)
    # Instantiate only the enabled cops so that disabled lazy-loaded cops are not loaded.
    # The registry is kept on standby to mobilize a disabled cop when a comment directive
    # opts it back in.
    cops = cop_classes.enabled(config).map { |cop_class| cop_class.new(config, options) }
    team = new(cops, config, options)
    team.standby_registry = cop_classes
    team
  else
    new(mobilize_cops(cop_classes, config, options), config, options)
  end
end

.mobilize_cops(cop_classes, config, options = {}) ⇒ Array<Cop::Base>

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 46

def self.mobilize_cops(cop_classes, config, options = {})
  cop_classes = Registry.new(cop_classes.to_a, options) unless cop_classes.is_a?(Registry)

  cop_classes.map do |cop_class|
    cop_class.new(config, options)
  end
end

Instance Attribute Details

#autocorrect?Boolean (readonly)

[ GitHub ]

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

def autocorrect?
  @options[:autocorrect]
end

#cops (readonly)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 69

attr_reader :errors, :warnings, :updated_source_file, :cops

#debug?Boolean (readonly)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 106

def debug?
  @options[:debug]
end

#defer_corrections (rw)

When set to true, the corrected source is not written back to the inspected file; it is exposed through #updated_source instead.

[ GitHub ]

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

attr_accessor :defer_corrections

#errors (readonly)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 69

attr_reader :errors, :warnings, :updated_source_file, :cops

#standby_registry=(value) (writeonly)

Registry used to mobilize cops that were not instantiated because they are disabled in the config, when a comment directive opts them back in for a file.

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 85

attr_writer :standby_registry

#updated_source (readonly)

The corrected source of the last investigation, if corrections were made with #defer_corrections enabled.

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 79

attr_reader :updated_source

#updated_source_file? (readonly)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 87

alias updated_source_file? updated_source_file

#warnings (readonly)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 69

attr_reader :errors, :warnings, :updated_source_file, :cops

Instance Method Details

#apply_correction(processed_source, new_source) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 174

def apply_correction(processed_source, new_source)
  if @options[:stdin]
    # holds source read in from stdin, when --stdin option is used
    @options[:stdin] = new_source
  elsif defer_corrections
    @updated_source = new_source
  else
    File.write(processed_source.file_path, new_source)
  end
end

#autocorrect(processed_source, corrector) (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 163

def autocorrect(processed_source, corrector)
  @updated_source_file = false
  @updated_source = nil
  return unless autocorrect?
  return unless corrector
  return if corrector.empty?

  apply_correction(processed_source, corrector.rewrite)
  @updated_source_file = true
end

#be_ready (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 185

def be_ready
  return if @ready

  reset
  @cops.map!(&:ready)
  @standby_cops.transform_values! { |cop| cop&.ready }
  @ready = true
end

#collate_corrections(report, offset:, original:) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 315

def collate_corrections(report, offset:, original:)
  corrector = Corrector.new(original)

  each_corrector(report) do |to_merge|
    merge_corrector!(corrector, to_merge, offset: offset)
  end

  corrector
end

#collated_corrector(report, offset:, original:) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 306

def collated_corrector(report, offset:, original:)
  return unless autocorrect?
  return if report.processed_source.parser_error

  corrector = collate_corrections(report, offset: offset, original: original)

  corrector unless corrector.empty?
end

#each_corrector(report) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 335

def each_corrector(report)
  skips = Set.new
  report.cop_reports.each do |cop_report|
    cop = cop_report.cop
    corrector = cop_report.corrector

    next if corrector.nil? || corrector.empty?
    next if skips.include?(cop.class)

    yield corrector

    skips.merge(cop.class.autocorrect_incompatible_with)
  end
end

#external_dependency_checksum

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 152

def external_dependency_checksum
  # The external dependency checksums are cached per RuboCop team so that
  # the checksums don't need to be recomputed for each file.
  @external_dependency_checksum ||= begin
    keys = cops.filter_map(&:external_dependency_checksum)
    Digest::SHA1.hexdigest(keys.join)
  end
end

#forces

Deprecated.
[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 144

def forces
  warn Rainbow(<<~WARNING).yellow, uplevel: 1
    `forces` is deprecated.
  WARNING

  @forces ||= self.class.forces_for(cops)
end

#handle_error(error, location, cop) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 387

def handle_error(error, location, cop)
  message = Rainbow("An error occurred while #{cop.name} cop was inspecting #{location}.").red
  @errors << message
  warn message
  if debug?
    puts error.full_message
  else
    warn 'To see the complete backtrace run rubocop -d.'
  end
end

#handle_warning(error, location) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 379

def handle_warning(error, location)
  message = Rainbow("#{error.message} (from file: #{location})").yellow

  @warnings << message
  warn message
  puts error.backtrace if debug?
end

#inspect_file(processed_source)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 112

def inspect_file(processed_source)
  warn Rainbow(<<~WARNING).yellow, uplevel: 1
    `inspect_file` is deprecated. Use `investigate` instead.
  WARNING

  investigate(processed_source).offenses
end

#investigate(processed_source, offset: 0, original: processed_source) ⇒ Commissioner::InvestigationReport

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 121

def investigate(processed_source, offset: 0, original: processed_source)
  result = investigate_with_corrector(processed_source, offset: offset, original: original)
  autocorrect(processed_source, result.corrector)
  result.report
end

#investigate_fragment(fragment, original, data) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 235

def investigate_fragment(fragment, original, data)
  offenses, errors, warnings, corrector = data
  result = investigate_with_corrector(
    fragment[:processed_source],
    offset: fragment[:offset],
    original: original
  )

  offenses.concat(result.report.offenses)
  if result.corrector
    corrector ||= Corrector.new(original)
    merge_corrector!(corrector, result.corrector, offset: 0)
    data[3] = corrector
  end
  errors.concat(@errors)
  warnings.concat(@warnings)
end

#investigate_fragments(fragments, original:) ⇒ Array<Offense>

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 128

def investigate_fragments(fragments, original:)
  @updated_source_file = false

  offenses, errors, warnings, corrector =
    fragments.each_with_object([[], [], [], nil]) do |fragment, data|
      investigate_fragment(fragment, original, data)
    end

  autocorrect(original, corrector)
  @errors = errors
  @warnings = warnings

  offenses
end

#investigate_partial(cops, processed_source, offset:, original:) ⇒ Commissioner::InvestigationReport (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 200

def investigate_partial(cops, processed_source, offset:, original:)
  commissioner = Commissioner.new(cops, self.class.forces_for(cops), @options)
  commissioner.investigate(processed_source, offset: offset, original: original)
end

#investigate_with_corrector(processed_source, offset:, original:) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 205

def investigate_with_corrector(processed_source, offset:, original:)
  be_ready

  # The autocorrection process may have to be repeated multiple times
  # until there are no corrections left to perform
  # To speed things up, run autocorrecting cops by themselves, and only
  # run the other cops when no corrections are left
  on_duty = roundup_relevant_cops(processed_source)

  autocorrect_cops, other_cops = on_duty.partition(&:autocorrect?)
  report = investigate_partial(autocorrect_cops, processed_source,
                               offset: offset, original: original)

  corrector = collated_corrector(report, offset: offset, original: original)

  unless corrector
    # If we corrected some errors, another round of inspection will be
    # done, and any other offenses will be caught then, so only need
    # to check other_cops if no correction was done
    report = report.merge(investigate_partial(other_cops, processed_source,
                                              offset: offset, original: original))
  end

  process_errors(processed_source.path, report.errors)

  InvestigationResult.new(report, corrector)
ensure
  @ready = false
end

#merge_corrector!(corrector, to_merge, offset:) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 325

def merge_corrector!(corrector, to_merge, offset:)
  suppress_clobbering do
    if corrector.source_buffer == to_merge.source_buffer
      corrector.merge!(to_merge)
    else
      corrector.import!(to_merge, offset: offset)
    end
  end
end

#opted_in_standby_cops(processed_source) ⇒ Array<cop> (private)

Cops that were not mobilized because they are disabled in the config, but are opted back in for the given file by an enable comment directive. Their classes are loaded on demand.

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 269

def opted_in_standby_cops(processed_source)
  return [] unless @standby_registry

  opted_in_names = processed_source.comment_config.opt_in_cops
  return [] if opted_in_names.empty?

  @mobilized_cop_names ||= Set.new(cops.map(&:cop_name))
  opted_in_names.filter_map do |cop_name|
    next if @mobilized_cop_names.include?(cop_name)

    standby_cop(cop_name)
  end
end

#process_errors(file, errors) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 362

def process_errors(file, errors)
  errors.each do |error|
    line = ":#{error.line}" if error.line
    column = ":#{error.column}" if error.column
    location = "#{file}#{line}#{column}"
    cause = error.cause

    if cause.is_a?(Warning)
      handle_warning(cause, location)
    elsif cause.is_a?(Force::HookError)
      handle_error(cause.cause, location, cause.joining_cop)
    else
      handle_error(cause, location, error.cop)
    end
  end
end

#reset (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 194

def reset
  @errors = []
  @warnings = []
end

#roundup_relevant_cops(processed_source) ⇒ Array<cop> (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 254

def roundup_relevant_cops(processed_source)
  (cops + opted_in_standby_cops(processed_source)).select do |cop|
    next false if cop.excluded_file?(processed_source.file_path)
    next true if processed_source.comment_config.cop_opted_in?(cop)
    next false unless @registry.enabled?(cop, @config)

    support_target_ruby_version?(cop) && support_target_rails_version?(cop)
  end
end

#standby_cop(cop_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 283

def standby_cop(cop_name)
  return @standby_cops[cop_name] if @standby_cops.key?(cop_name)

  cop_class = @standby_registry.find_by_cop_name(cop_name)

  @standby_cops[cop_name] = cop_class&.new(@config, @options)
end

#support_target_rails_version?(cop) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 297

def support_target_rails_version?(cop)
  # In this case, the rails version was already checked by `#excluded_file?`
  return true if defined?(RuboCop::Rails::TargetRailsVersion::USES_REQUIRES_GEM_API)

  return true unless cop.class.respond_to?(:support_target_rails_version?)

  cop.class.support_target_rails_version?(cop.target_rails_version)
end

#support_target_ruby_version?(cop) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 291

def support_target_ruby_version?(cop)
  return true unless cop.class.respond_to?(:support_target_ruby_version?)

  cop.class.support_target_ruby_version?(cop.target_ruby_version)
end

#suppress_clobbering (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 350

def suppress_clobbering
  yield
rescue ::Parser::ClobberingError
  # ignore Clobbering errors
end

#updated_source_file (readonly) Also known as: #updated_source_file?

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 69

attr_reader :errors, :warnings, :updated_source_file, :cops

#validate_config (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/team.rb', line 356

def validate_config
  cops.each do |cop|
    cop.validate_config if cop.respond_to?(:validate_config)
  end
end