class RuboCop::Cop::Base

A scaffold for concrete cops.

The Cop::Base class is meant to be extended.

Cops track offenses and can autocorrect them on the fly.

A commissioner object is responsible for traversing the AST and invoking the specific callbacks on each cop.

First the callback ‘on_new_investigation` is called; if a cop needs to do its own processing of the AST or depends on something else.

Then callbacks like ‘on_def`, `on_send` (see AST::Traversal) are called with their respective nodes.

Finally the callback ‘on_investigation_end` is called.

Within these callbacks, cops are meant to call ‘add_offense` or `add_global_offense`. Use the `processed_source` method to get the currently processed source being investigated.

In case of invalid syntax / unparsable content, the callback ‘on_other_file` is called instead of all the other `on_…` callbacks.

Private methods are not meant for custom cops consumption, nor are any instance variables.

Constants

EMPTY_OFFENSES
InvestigationReport

Reports of an investigation. Immutable Consider creation API private

RESTRICT_ON_SEND

List of methods names to restrict calls for ‘on_send` / `on_csend`

Attributes

gem_requirements[R]
config[R]
processed_source[R]

Public Class Methods

autocorrect_incompatible_with() click to toggle source

List of cops that should not try to autocorrect at the same time as this cop

@return [Array<RuboCop::Cop::Base>]

@api public

# File lib/rubocop/cop/base.rb, line 59
def self.autocorrect_incompatible_with
  []
end
badge() click to toggle source

Naming

# File lib/rubocop/cop/base.rb, line 93
def self.badge
  @badge ||= Badge.for(name)
end
callbacks_needed() click to toggle source

@api private

# File lib/rubocop/cop/base.rb, line 323
def self.callbacks_needed
  @callbacks_needed ||= public_instance_methods.select do |m|
    # OPTIMIZE: Check method existence first to make fewer `start_with?` calls.
    # At the time of writing this comment, this excludes 98 of ~104 methods.
    # `start_with?` with two string arguments instead of a regex is faster
    # in this specific case as well.
    !Base.method_defined?(m) && # exclude standard "callbacks" like 'on_begin_investigation'
      m.start_with?('on_', 'after_')
  end
end
cop_name() click to toggle source
# File lib/rubocop/cop/base.rb, line 97
def self.cop_name
  badge.to_s
end
department() click to toggle source
# File lib/rubocop/cop/base.rb, line 101
def self.department
  badge.department
end
documentation_url(config = nil) click to toggle source

Returns an url to view this cops documentation online. Requires ‘DocumentationBaseURL’ to be set for your department. Will follow the convention of RuboCops own documentation structure, overwrite this method to accommodate your custom layout. @return [String, nil]

@api public

# File lib/rubocop/cop/base.rb, line 70
def self.documentation_url(config = nil)
  Documentation.url_for(self, config)
end
exclude_from_registry() click to toggle source

Call for abstract Cop classes

# File lib/rubocop/cop/base.rb, line 81
def self.exclude_from_registry
  Registry.global.dismiss(self)
end
inherited(subclass) click to toggle source
Calls superclass method
# File lib/rubocop/cop/base.rb, line 74
def self.inherited(subclass)
  super
  subclass.instance_variable_set(:@gem_requirements, gem_requirements.dup)
  Registry.global.enlist(subclass)
end
joining_forces() click to toggle source

Override and return the Force class(es) you need to join

# File lib/rubocop/cop/base.rb, line 118
def self.joining_forces; end
lint?() click to toggle source
# File lib/rubocop/cop/base.rb, line 105
def self.lint?
  department == :Lint
end
match?(given_names) click to toggle source

Returns true if the cop name or the cop namespace matches any of the given names.

# File lib/rubocop/cop/base.rb, line 111
def self.match?(given_names)
  return false unless given_names

  given_names.include?(cop_name) || given_names.include?(badge.department_name)
end
new(config = nil, options = nil) click to toggle source
# File lib/rubocop/cop/base.rb, line 156
def initialize(config = nil, options = nil)
  @config = config || Config.new
  @options = options || { debug: false }
  reset_investigation
end
requires_gem(gem_name, *version_requirements) click to toggle source

Register a version requirement for the given gem name. This cop will be skipped unless the target satisfies all requirements. @param [String] gem_name @param [Array<String>] version_requirements The version requirements,

using the same syntax as a Gemfile, e.g. ">= 1.2.3"

If omitted, any version of the gem will be accepted.

https://guides.rubygems.org/patterns/#declaring-dependencies

@api public

# File lib/rubocop/cop/base.rb, line 151
def requires_gem(gem_name, *version_requirements)
  @gem_requirements[gem_name] = Gem::Requirement.new(version_requirements)
end
restrict_on_send() click to toggle source
# File lib/rubocop/cop/base.rb, line 395
                     def self.restrict_on_send
  @restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze
end
support_autocorrect?() click to toggle source

Returns if class supports autocorrect. It is recommended to extend AutoCorrector instead of overriding

# File lib/rubocop/cop/base.rb, line 87
def self.support_autocorrect?
  false
end
support_multiple_source?() click to toggle source

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

# File lib/rubocop/cop/base.rb, line 129
def self.support_multiple_source?
  false
end

Public Instance Methods

active_support_extensions_enabled?() click to toggle source
# File lib/rubocop/cop/base.rb, line 272
def active_support_extensions_enabled?
  @config.active_support_extensions_enabled?
end
add_global_offense(message = nil, severity: nil) click to toggle source

Adds an offense that has no particular location. No correction can be applied to global offenses

# File lib/rubocop/cop/base.rb, line 189
def add_global_offense(message = nil, severity: nil)
  severity = find_severity(nil, severity)
  message = find_message(nil, message)
  range = Offense::NO_LOCATION
  status = enabled_line?(range.line) ? :unsupported : :disabled
  current_offenses << Offense.new(severity, range, message, name, status)
end
add_offense(node_or_range, message: nil, severity: nil, &block) click to toggle source

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense. If message is not specified, the method ‘message` will be called.

# File lib/rubocop/cop/base.rb, line 201
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
always_autocorrect?() click to toggle source

@api private

# File lib/rubocop/cop/base.rb, line 351
def always_autocorrect?
  # `true` is the same as `'always'` for backward compatibility.
  ['always', true].include?(cop_config.fetch('AutoCorrect', 'always'))
end
begin_investigation(processed_source, offset: 0, original: processed_source) click to toggle source

Called before any investigation @api private

# File lib/rubocop/cop/base.rb, line 337
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
callbacks_needed() click to toggle source

rubocop:disable Layout/ClassStructure @api private

# File lib/rubocop/cop/base.rb, line 318
def callbacks_needed
  self.class.callbacks_needed
end
config_to_allow_offenses() click to toggle source
# File lib/rubocop/cop/base.rb, line 252
def config_to_allow_offenses
  Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] ||= {}
end
config_to_allow_offenses=(hash) click to toggle source
# File lib/rubocop/cop/base.rb, line 256
def config_to_allow_offenses=(hash)
  Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] = hash
end
contextual_autocorrect?() click to toggle source

@api private

# File lib/rubocop/cop/base.rb, line 357
def contextual_autocorrect?
  cop_config.fetch('AutoCorrect', 'always') == 'contextual'
end
cop_config() click to toggle source

Configuration Helpers

# File lib/rubocop/cop/base.rb, line 246
def cop_config
  # Use department configuration as basis, but let individual cop
  # configuration override.
  @cop_config ||= @config.for_badge(self.class.badge)
end
cop_name() click to toggle source
# File lib/rubocop/cop/base.rb, line 238
def cop_name
  @cop_name ||= self.class.cop_name
end
Also aliased as: name
excluded_file?(file) click to toggle source
# File lib/rubocop/cop/base.rb, line 289
def excluded_file?(file)
  !relevant_file?(file)
end
external_dependency_checksum() click to toggle source

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

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

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

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

# File lib/rubocop/cop/base.rb, line 234
def external_dependency_checksum
  nil
end
message(_range = nil) click to toggle source

Gets called if no message is specified when calling ‘add_offense` or `add_global_offense` Cops are discouraged to override this; instead pass your message directly

# File lib/rubocop/cop/base.rb, line 183
def message(_range = nil)
  self.class::MSG
end
name()
Alias for: cop_name
offenses() click to toggle source

@deprecated Make potential errors with previous API more obvious

# File lib/rubocop/cop/base.rb, line 309
def offenses
  raise 'The offenses are not directly available; ' \
        'they are returned as the result of the investigation'
end
on_investigation_end() click to toggle source

Called after all on_… have been called When refining this method, always call ‘super`

# File lib/rubocop/cop/base.rb, line 170
def on_investigation_end
  # Typically do nothing here
end
on_new_investigation() click to toggle source

Called before all on_… have been called When refining this method, always call ‘super`

# File lib/rubocop/cop/base.rb, line 164
def on_new_investigation
  # Typically do nothing here
end
on_other_file() click to toggle source

Called instead of all on_… callbacks for unrecognized files / syntax errors When refining this method, always call ‘super`

# File lib/rubocop/cop/base.rb, line 176
def on_other_file
  # Typically do nothing here
end
parse(source, path = nil) click to toggle source

There should be very limited reasons for a Cop to do it’s own parsing

# File lib/rubocop/cop/base.rb, line 294
def parse(source, path = nil)
  ProcessedSource.new(source, target_ruby_version, path, parser_engine: parser_engine)
end
parser_engine() click to toggle source
# File lib/rubocop/cop/base.rb, line 264
def parser_engine
  @config.parser_engine
end
ready() click to toggle source

@api private Called between investigations

# File lib/rubocop/cop/base.rb, line 300
def ready
  return self if self.class.support_multiple_source?

  self.class.new(@config, @options)
end
relevant_file?(file) click to toggle source
# File lib/rubocop/cop/base.rb, line 280
def relevant_file?(file)
  return false unless target_satisfies_all_gem_version_requirements?
  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
string_literals_frozen_by_default?() click to toggle source
# File lib/rubocop/cop/base.rb, line 276
def string_literals_frozen_by_default?
  @config.string_literals_frozen_by_default?
end
target_rails_version() click to toggle source
# File lib/rubocop/cop/base.rb, line 268
def target_rails_version
  @config.target_rails_version
end
target_ruby_version() click to toggle source
# File lib/rubocop/cop/base.rb, line 260
def target_ruby_version
  @config.target_ruby_version
end

Private Instance Methods

annotate(message) click to toggle source
# File lib/rubocop/cop/base.rb, line 479
def annotate(message)
  RuboCop::Cop::MessageAnnotator.new(
    config, cop_name, cop_config, @options
  ).annotate(message)
end
apply_correction(corrector) click to toggle source
# File lib/rubocop/cop/base.rb, line 373
def apply_correction(corrector)
  current_corrector&.merge!(corrector) if corrector
end
attempt_correction(range, corrector) click to toggle source

@return [Symbol] offense status

# File lib/rubocop/cop/base.rb, line 443
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
callback_argument(range) click to toggle source

Reserved for Cop::Cop

# File lib/rubocop/cop/base.rb, line 369
def callback_argument(range)
  range
end
complete_investigation() click to toggle source

Called to complete an investigation

# File lib/rubocop/cop/base.rb, line 402
def complete_investigation
  InvestigationReport.new(
    self, processed_source, @current_offenses || EMPTY_OFFENSES, @current_corrector
  )
ensure
  reset_investigation
end
correct(range) { |corrector| ... } click to toggle source

@return [Symbol, Corrector] offense status

# File lib/rubocop/cop/base.rb, line 417
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
current_corrector() click to toggle source
# File lib/rubocop/cop/base.rb, line 387
def current_corrector
  @current_corrector ||= Corrector.new(@processed_source) if @processed_source.valid_syntax?
end
current_offense_locations() click to toggle source

Reserved for Commissioner:

# File lib/rubocop/cop/base.rb, line 379
def current_offense_locations
  @current_offense_locations ||= Set.new
end
current_offenses() click to toggle source
# File lib/rubocop/cop/base.rb, line 391
def current_offenses
  @current_offenses ||= []
end
currently_disabled_lines() click to toggle source
# File lib/rubocop/cop/base.rb, line 383
def currently_disabled_lines
  @currently_disabled_lines ||= Set.new
end
custom_severity() click to toggle source
# File lib/rubocop/cop/base.rb, line 507
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
default_severity() click to toggle source
# File lib/rubocop/cop/base.rb, line 503
def default_severity
  self.class.lint? ? :warning : :convention
end
disable_uncorrectable(range) click to toggle source
# File lib/rubocop/cop/base.rb, line 457
def disable_uncorrectable(range)
  line = range.line
  return unless currently_disabled_lines.add?(line)

  disable_offense(range)
end
enabled_line?(line_number) click to toggle source
# File lib/rubocop/cop/base.rb, line 493
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
file_name_matches_any?(file, parameter, default_result) click to toggle source
# File lib/rubocop/cop/base.rb, line 485
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
find_message(range, message) click to toggle source
# File lib/rubocop/cop/base.rb, line 475
def find_message(range, message)
  annotate(message || message(range))
end
find_severity(_range, severity) click to toggle source
# File lib/rubocop/cop/base.rb, line 499
def find_severity(_range, severity)
  custom_severity || severity || default_severity
end
range_for_original(range) click to toggle source
# File lib/rubocop/cop/base.rb, line 520
def range_for_original(range)
  ::Parser::Source::Range.new(
    @current_original.buffer,
    range.begin_pos + @current_offset,
    range.end_pos + @current_offset
  )
end
range_from_node_or_range(node_or_range) click to toggle source
# File lib/rubocop/cop/base.rb, line 464
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
reset_investigation() click to toggle source

Actually private methods

# File lib/rubocop/cop/base.rb, line 412
def reset_investigation
  @currently_disabled_lines = @current_offenses = @processed_source = @current_corrector = nil
end
target_satisfies_all_gem_version_requirements?() click to toggle source
# File lib/rubocop/cop/base.rb, line 528
def target_satisfies_all_gem_version_requirements?
  self.class.gem_requirements.all? do |gem_name, version_req|
    all_gem_versions_in_target = @config.gem_versions_in_target
    next false unless all_gem_versions_in_target

    gem_version_in_target = all_gem_versions_in_target[gem_name]
    next false unless gem_version_in_target

    version_req.satisfied_by?(gem_version_in_target)
  end
end
use_corrector(range, corrector) click to toggle source

@return [Symbol] offense status

# File lib/rubocop/cop/base.rb, line 432
def use_corrector(range, corrector)
  if autocorrect?
    attempt_correction(range, corrector)
  elsif corrector && (always_autocorrect? || (contextual_autocorrect? && !LSP.enabled?))
    :uncorrected
  else
    :unsupported
  end
end