class RuboCop::Cop::Base

rubocop:disable Metrics/ClassLength
nor are any instance variables.
Private methods are not meant for custom cops consumption,
‘on_…` callbacks.
the callback `on_other_file` is called instead of all the other
In case of invalid syntax / unparsable content,
get the currently processed source being investigated.
`add_global_offense`. Use the `processed_source` method to
Within these callbacks, cops are meant to call `add_offense` or
Finally the callback `on_investigation_end` is called.
with their respective nodes.
Then callbacks like `on_def`, `on_send` (see AST::Traversal) are called
something else.
if a cop needs to do its own processing of the AST or depends on
First the callback `on_new_investigation` is called;
the specific callbacks on each cop.
A commissioner object is responsible for traversing the AST and invoking
Cops track offenses and can autocorrect them on the fly.
The Cop::Base class is meant to be extended.
A scaffold for concrete cops.

def self.autocorrect_incompatible_with

Other tags:
    Api: - public

Returns:
  • (Array) -
def self.autocorrect_incompatible_with
  []
end

def self.badge

def self.badge
  @badge ||= Badge.for(name)
end

def self.builtin?

rubocop:disable Layout/ClassStructure
def self.builtin?
  return false unless (m = instance_methods(false).first) # any custom method will do
  path, _line = instance_method(m).source_location
  path.start_with?(__dir__)
end

def self.callbacks_needed

Other tags:
    Api: - private
def self.callbacks_needed
  @callbacks_needed ||= public_instance_methods.select do |m|
    m.start_with?(/on_|after_/) &&
      !Base.method_defined?(m) # exclude standard "callbacks" like 'on_begin_investigation'
  end
end

def self.cop_name

def self.cop_name
  badge.to_s
end

def self.department

def self.department
  badge.department
end

def self.documentation_url

Other tags:
    Api: - public

Returns:
  • (String, nil) -
def self.documentation_url
  Documentation.url_for(self) if builtin?
end

def self.exclude_from_registry

Call for abstract Cop classes
def self.exclude_from_registry
  Registry.global.dismiss(self)
end

def self.inherited(subclass)

def self.inherited(subclass)
  super
  Registry.global.enlist(subclass)
end

def self.joining_forces; end

Override and return the Force class(es) you need to join
def self.joining_forces; end

def self.lint?

def self.lint?
  department == :Lint
end

def self.match?(given_names)

given names.
Returns true if the cop name or the cop namespace matches any of the
def self.match?(given_names)
  return false unless given_names
  given_names.include?(cop_name) || given_names.include?(badge.department_name)
end

def self.restrict_on_send

def self.restrict_on_send
|= self::RESTRICT_ON_SEND.to_a.freeze

def self.support_autocorrect?

It is recommended to extend AutoCorrector instead of overriding
Returns if class supports autocorrect.
def self.support_autocorrect?
  false
end

def self.support_multiple_source?

multiple times with the same `processed_source.path` but different content.
If your cop does autocorrections, be aware that your instance may be called
in the `on_new_investigation` callback.
You should invalidate any caches that depend on the current `processed_source`
the result of `processed_source` will remain constant.
Between calls to `on_new_investigation` and `on_investigation_end`,
Override if your cop should be called repeatedly for multiple investigations
def self.support_multiple_source?
  false
end

def active_support_extensions_enabled?

def active_support_extensions_enabled?
  @config.active_support_extensions_enabled?
end

def add_global_offense(message = nil, severity: nil)

No correction can be applied to global offenses
Adds an offense that has no particular location.
def add_global_offense(message = nil, severity: nil)
  severity = find_severity(nil, severity)
  message = find_message(nil, message)
  current_offenses << Offense.new(severity, Offense::NO_LOCATION, message, name, :unsupported)
end

def add_offense(node_or_range, message: nil, severity: nil, &block)

If message is not specified, the method `message` will be called.
to provide the cop the opportunity to autocorrect the offense.
Unless that offense is disabled for this range, a corrector will be yielded
Adds an offense on the specified range (or node with an expression)
def add_offense(node_or_range, message: nil, severity: nil, &block)
  range = range_from_node_or_range(node_or_range)
  return unless current_offense_locations.add?(range)
  range_to_pass = callback_argument(range)
  severity = find_severity(range_to_pass, severity)
  message = find_message(range_to_pass, message)
  status, corrector = enabled_line?(range.line) ? correct(range, &block) : :disabled
  # Since this range may be generated from Ruby code embedded in some
  # template file, we convert it to location info in the original file.
  range = range_for_original(range)
  current_offenses << Offense.new(severity, range, message, name, status, corrector)
end

def annotate(message)

def annotate(message)
  RuboCop::Cop::MessageAnnotator.new(
    config, cop_name, cop_config, @options
  ).annotate(message)
end

def apply_correction(corrector)

def apply_correction(corrector)
  current_corrector&.merge!(corrector) if corrector
end

def attempt_correction(range, corrector)

Returns:
  • (Symbol) - offense status
def attempt_correction(range, corrector)
  if corrector
    status = :corrected
  elsif disable_uncorrectable?
    corrector = disable_uncorrectable(range)
    status = :corrected_with_todo
  else
    return :unsupported
  end
  apply_correction(corrector)
  status
end

def begin_investigation(processed_source, offset: 0, original: processed_source)

Other tags:
    Api: - private
def begin_investigation(processed_source, offset: 0, original: processed_source)
  @current_offenses = nil
  @current_offense_locations = nil
  @currently_disabled_lines = nil
  @processed_source = processed_source
  @current_corrector = nil
  # We need to keep track of the original source and offset,
  # because `processed_source` here may be an embedded code in it.
  @current_offset = offset
  @current_original = original
end

def callback_argument(range)

def callback_argument(range)
  range
end

def callbacks_needed

Other tags:
    Api: - private
def callbacks_needed
  self.class.callbacks_needed
end

def complete_investigation

Called to complete an investigation
def complete_investigation
  InvestigationReport.new(
    self, processed_source, @current_offenses || EMPTY_OFFENSES, @current_corrector
  )
ensure
  reset_investigation
end

def config_to_allow_offenses

def config_to_allow_offenses
  Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] ||= {}
end

def config_to_allow_offenses=(hash)

def config_to_allow_offenses=(hash)
  Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] = hash
end

def cop_config

def cop_config
  # Use department configuration as basis, but let individual cop
  # configuration override.
  @cop_config ||= @config.for_badge(self.class.badge)
end

def cop_name

def cop_name
  @cop_name ||= self.class.cop_name
end

def correct(range)

Returns:
  • (Symbol, Corrector) - offense status
def correct(range)
  if block_given?
    corrector = Corrector.new(self)
    yield corrector
    if corrector.empty?
      corrector = nil
    elsif !self.class.support_autocorrect?
      raise "The Cop #{name} must `extend AutoCorrector` to be able to autocorrect"
    end
  end
  [use_corrector(range, corrector), corrector]
end

def current_corrector

def current_corrector
  @current_corrector ||= Corrector.new(@processed_source) if @processed_source.valid_syntax?
end

def current_offense_locations

def current_offense_locations
  @current_offense_locations ||= Set.new
end

def current_offenses

def current_offenses
  @current_offenses ||= []
end

def currently_disabled_lines

def currently_disabled_lines
  @currently_disabled_lines ||= Set.new
end

def custom_severity

def custom_severity
  severity = cop_config['Severity']
  return unless severity
  if Severity::NAMES.include?(severity.to_sym)
    severity.to_sym
  else
    message = "Warning: Invalid severity '#{severity}'. " \
              "Valid severities are #{Severity::NAMES.join(', ')}."
    warn(Rainbow(message).red)
  end
end

def default_severity

def default_severity
  self.class.lint? ? :warning : :convention
end

def disable_uncorrectable(range)

def disable_uncorrectable(range)
  line = range.line
  return unless currently_disabled_lines.add?(line)
  disable_offense(range)
end

def enabled_line?(line_number)

def enabled_line?(line_number)
  return true if @options[:ignore_disable_comments] || !@processed_source
  @processed_source.comment_config.cop_enabled_at_line?(self, line_number)
end

def excluded_file?(file)

def excluded_file?(file)
  !relevant_file?(file)
end

def external_dependency_checksum

ie when the ResultCache should be invalidated.
ResultCache system when those external dependencies change,
Overriding this method allows the cop to indicate to RuboCop's

whether a certain violation exists in `bar.rb`.
use the presence or absence of file `foo.rb` to determine
the codebase being inspected to find violations. A cop may
For example, some cops may want to look at other parts of

(3) the config (eg a .rubocop.yml file)
(2) the cop's source code
(1) the file under inspection

on state that lives outside of these locations:
This method should be overridden when a cop's behavior depends
def external_dependency_checksum
  nil
end

def file_name_matches_any?(file, parameter, default_result)

def file_name_matches_any?(file, parameter, default_result)
  patterns = cop_config[parameter]
  return default_result unless patterns
  patterns = FilePatterns.from(patterns)
  patterns.match?(config.path_relative_to_config(file)) || patterns.match?(file)
end

def find_message(range, message)

def find_message(range, message)
  annotate(message || message(range))
end

def find_severity(_range, severity)

def find_severity(_range, severity)
  custom_severity || severity || default_severity
end

def initialize(config = nil, options = nil)

def initialize(config = nil, options = nil)
  @config = config || Config.new
  @options = options || { debug: false }
  reset_investigation
end

def inspect # :nodoc:

:nodoc:
def inspect # :nodoc:
  "#<#{self.class.name}:#{object_id} @config=#{@config} @options=#{@options}>"
end

def message(_range = nil)

Cops are discouraged to override this; instead pass your message directly
`add_global_offense`
Gets called if no message is specified when calling `add_offense` or
def message(_range = nil)
  self.class::MSG
end

def offenses

Deprecated:
  • Make potential errors with previous API more obvious
def offenses
  raise 'The offenses are not directly available; ' \
        'they are returned as the result of the investigation'
end

def on_investigation_end

When refining this method, always call `super`
Called after all on_... have been called
def on_investigation_end
  # Typically do nothing here
end

def on_new_investigation

When refining this method, always call `super`
Called before all on_... have been called
def on_new_investigation
  # Typically do nothing here
end

def on_other_file

When refining this method, always call `super`
Called instead of all on_... callbacks for unrecognized files / syntax errors
def on_other_file
  # Typically do nothing here
end

def parse(source, path = nil)

There should be very limited reasons for a Cop to do it's own parsing
def parse(source, path = nil)
  ProcessedSource.new(source, target_ruby_version, path)
end

def range_for_original(range)

def range_for_original(range)
  ::Parser::Source::Range.new(
    @current_original.buffer,
    range.begin_pos + @current_offset,
    range.end_pos + @current_offset
  )
end

def range_from_node_or_range(node_or_range)

def range_from_node_or_range(node_or_range)
  if node_or_range.respond_to?(:loc)
    node_or_range.source_range
  elsif node_or_range.is_a?(::Parser::Source::Range)
    node_or_range
  else
    extra = ' (call `add_global_offense`)' if node_or_range.nil?
    raise "Expected a Source::Range, got #{node_or_range.inspect}#{extra}"
  end
end

def ready

Other tags:
    Api: - private
def ready
  return self if self.class.support_multiple_source?
  self.class.new(@config, @options)
end

def relevant_file?(file)

def relevant_file?(file)
  return true unless @config.clusivity_config_for_badge?(self.class.badge)
  file == RuboCop::AST::ProcessedSource::STRING_SOURCE_NAME ||
    (file_name_matches_any?(file, 'Include', true) &&
      !file_name_matches_any?(file, 'Exclude', false))
end

def reset_investigation

def reset_investigation
  @currently_disabled_lines = @current_offenses = @processed_source = @current_corrector = nil
end

def target_rails_version

def target_rails_version
  @config.target_rails_version
end

def target_ruby_version

def target_ruby_version
  @config.target_ruby_version
end

def use_corrector(range, corrector)

Returns:
  • (Symbol) - offense status
def use_corrector(range, corrector)
  if autocorrect?
    attempt_correction(range, corrector)
  elsif corrector && cop_config.fetch('AutoCorrect', true)
    :uncorrected
  else
    :unsupported
  end
end