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

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

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::Cop>]

@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 169
def self.badge
  @badge ||= Badge.for(name)
end
callbacks_needed() click to toggle source

@api private

# File lib/rubocop/cop/base.rb, line 279
def self.callbacks_needed
  @callbacks_needed ||= public_instance_methods.select do |m|
    m.match?(/^on_|^after_/) &&
      !Base.method_defined?(m) # exclude standard "callbacks" like 'on_begin_investigation'
  end
end
cop_name() click to toggle source
# File lib/rubocop/cop/base.rb, line 173
def self.cop_name
  badge.to_s
end
department() click to toggle source
# File lib/rubocop/cop/base.rb, line 177
def self.department
  badge.department
end
documentation_url() click to toggle source

Cops (other than builtin) are encouraged to implement this @return [String, nil]

@api public

# File lib/rubocop/cop/base.rb, line 67
def self.documentation_url
  Documentation.url_for(self) if builtin?
end
exclude_from_registry() click to toggle source

Call for abstract Cop classes

# File lib/rubocop/cop/base.rb, line 157
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 151
def self.inherited(subclass)
  super
  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 96
def self.joining_forces; end
lint?() click to toggle source
# File lib/rubocop/cop/base.rb, line 181
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 187
def self.match?(given_names)
  return false unless given_names

  given_names.include?(cop_name) || given_names.include?(department.to_s)
end
new(config = nil, options = nil) click to toggle source
# File lib/rubocop/cop/base.rb, line 71
def initialize(config = nil, options = nil)
  @config = config || Config.new
  @options = options || { debug: false }
  reset_investigation
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 163
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 251
def self.support_multiple_source?
  false
end

Private Class Methods

builtin?() click to toggle source

Actually private methods

# File lib/rubocop/cop/base.rb, line 330
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
restrict_on_send() click to toggle source
# File lib/rubocop/cop/base.rb, line 308
                     def self.restrict_on_send
  @restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze
end

Public Instance Methods

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

  @current_offenses << Offense.new(severity, range, message, name, status, corrector)
end
callbacks_needed() click to toggle source

@api private

# File lib/rubocop/cop/base.rb, line 274
def callbacks_needed
  self.class.callbacks_needed
end
config_to_allow_offenses() click to toggle source
# File lib/rubocop/cop/base.rb, line 207
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 211
def config_to_allow_offenses=(hash)
  Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] = hash
end
cop_config() click to toggle source

Configuration Helpers

# File lib/rubocop/cop/base.rb, line 201
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 193
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 233
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 147
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 101
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 266
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 85
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 79
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 91
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 238
def parse(source, path = nil)
  ProcessedSource.new(source, target_ruby_version, path)
end
ready() click to toggle source

@api private Called between investigations

# File lib/rubocop/cop/base.rb, line 257
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 227
def relevant_file?(file)
  file == RuboCop::AST::ProcessedSource::STRING_SOURCE_NAME ||
    (file_name_matches_any?(file, 'Include', true) &&
      !file_name_matches_any?(file, 'Exclude', false))
end
target_rails_version() click to toggle source
# File lib/rubocop/cop/base.rb, line 219
def target_rails_version
  @config.target_rails_version
end
target_ruby_version() click to toggle source
# File lib/rubocop/cop/base.rb, line 215
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 405
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 294
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 369
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
begin_investigation(processed_source) click to toggle source

Called before any investigation

# File lib/rubocop/cop/base.rb, line 313
def begin_investigation(processed_source)
  @current_offenses = []
  @current_offense_locations = nil
  @currently_disabled_lines = nil
  @processed_source = processed_source
  @current_corrector = Corrector.new(@processed_source) if @processed_source.valid_syntax?
end
callback_argument(range) click to toggle source

Reserved for Cop::Cop

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

Called to complete an investigation

# File lib/rubocop/cop/base.rb, line 322
def complete_investigation
  InvestigationReport.new(self, processed_source, @current_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 343
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_offense_locations() click to toggle source

Reserved for Commissioner:

# File lib/rubocop/cop/base.rb, line 300
def current_offense_locations
  @current_offense_locations ||= Set.new
end
currently_disabled_lines() click to toggle source
# File lib/rubocop/cop/base.rb, line 304
def currently_disabled_lines
  @currently_disabled_lines ||= Set.new
end
custom_severity() click to toggle source
# File lib/rubocop/cop/base.rb, line 440
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 436
def default_severity
  self.class.lint? ? :warning : :convention
end
disable_uncorrectable(range) click to toggle source
# File lib/rubocop/cop/base.rb, line 383
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 426
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 411
def file_name_matches_any?(file, parameter, default_result)
  patterns = cop_config[parameter]
  return default_result unless patterns

  path = nil
  patterns.any? do |pattern|
    # Try to match the absolute path, as Exclude properties are absolute.
    next true if match_path?(pattern, file)

    # Try with relative path.
    path ||= config.path_relative_to_config(file)
    match_path?(pattern, path)
  end
end
find_message(range, message) click to toggle source
# File lib/rubocop/cop/base.rb, line 401
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 432
def find_severity(_range, severity)
  custom_severity || severity || default_severity
end
range_from_node_or_range(node_or_range) click to toggle source
# File lib/rubocop/cop/base.rb, line 390
def range_from_node_or_range(node_or_range)
  if node_or_range.respond_to?(:loc)
    node_or_range.loc.expression
  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
# File lib/rubocop/cop/base.rb, line 338
def reset_investigation
  @currently_disabled_lines = @current_offenses = @processed_source = @current_corrector = nil
end
use_corrector(range, corrector) click to toggle source

@return [Symbol] offense status

# File lib/rubocop/cop/base.rb, line 358
def use_corrector(range, corrector)
  if autocorrect?
    attempt_correction(range, corrector)
  elsif corrector && cop_config.fetch('AutoCorrect', true)
    :uncorrected
  else
    :unsupported
  end
end