Class: RuboCop::Cop::Base
Overview
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.
Constant Summary
-
EMPTY_OFFENSES =
private
# File 'lib/rubocop/cop/base.rb', line 388[].freeze
-
RESTRICT_ON_SEND =
List of methods names to restrict calls for
on_send
/on_csend
Set[].freeze
Class Attribute Summary
- .gem_requirements readonly
- .lint? ⇒ Boolean readonly
-
.support_autocorrect? ⇒ Boolean
readonly
Returns if class supports autocorrect.
-
.support_multiple_source? ⇒ Boolean
readonly
Override if your cop should be called repeatedly for multiple investigations Between calls to
on_new_investigation
andon_investigation_end
, the result ofprocessed_source
will remain constant. - .builtin? ⇒ Boolean readonly private
Class Method Summary
-
.autocorrect_incompatible_with ⇒ Array<RuboCop::Cop::Base>
List of cops that should not try to autocorrect at the same time as this cop.
-
.badge
Naming.
- .callbacks_needed Internal use only Internal use only
- .cop_name
- .department
-
.documentation_url ⇒ String?
Cops (other than builtin) are encouraged to implement this.
-
.exclude_from_registry
Call for abstract Cop classes.
- .inherited(subclass)
-
.joining_forces
Override and return the Force class(es) you need to join.
-
.match?(given_names) ⇒ Boolean
Returns true if the cop name or the cop namespace matches any of the given names.
- .new(config = nil, options = nil) ⇒ Base constructor
-
.requires_gem(gem_name, *version_requirements)
Register a version requirement for the given gem name.
- .restrict_on_send private
::RuboCop::ExcludeLimit
- Extended
exclude_limit | Sets up a configuration option to have an exclude limit tracked. |
transform |
Instance Attribute Summary
- #active_support_extensions_enabled? ⇒ Boolean readonly
- #always_autocorrect? ⇒ Boolean readonly Internal use only Internal use only
- #config readonly
- #config_to_allow_offenses rw
- #config_to_allow_offenses=(hash) rw
- #contextual_autocorrect? ⇒ Boolean readonly Internal use only Internal use only
- #processed_source readonly
- #target_satisfies_all_gem_version_requirements? ⇒ Boolean readonly private
AutocorrectLogic
- Included
Instance Method Summary
-
#add_global_offense(message = nil, severity: nil)
Adds an offense that has no particular location.
-
#add_offense(node_or_range, message: nil, severity: nil, &block)
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.
-
#begin_investigation(processed_source, offset: 0, original: processed_source)
Internal use only
Internal use only
Called before any investigation.
- #callbacks_needed Internal use only Internal use only
-
#cop_config
Configuration Helpers.
- #cop_name (also: #name)
- #excluded_file?(file) ⇒ Boolean
-
#external_dependency_checksum
This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:
- #inspect Internal use only
-
#message(_range = nil)
Gets called if no message is specified when calling
add_offense
oradd_global_offense
Cops are discouraged to override this; instead pass your message directly. -
#name
Alias for #cop_name.
-
#offenses
deprecated
Deprecated.
Make potential errors with previous API more obvious
-
#on_investigation_end
Called after all on_…
-
#on_new_investigation
Called before all on_…
-
#on_other_file
Called instead of all on_…
-
#parse(source, path = nil)
There should be very limited reasons for a Cop to do it’s own parsing.
- #parser_engine
-
#ready
Internal use only
Internal use only
Called between investigations.
- #relevant_file?(file) ⇒ Boolean
- #target_rails_version
- #target_ruby_version
- #annotate(message) private
- #apply_correction(corrector) private
- #attempt_correction(range, corrector) ⇒ Symbol private
-
#callback_argument(range)
private
Reserved for Cop::Cop.
-
#complete_investigation
private
Called to complete an investigation.
- #correct(range) ⇒ Symbol, Corrector private
- #current_corrector private
-
#current_offense_locations
private
Reserved for Commissioner:
- #current_offenses private
- #currently_disabled_lines private
- #custom_severity private
- #default_severity private
- #disable_uncorrectable(range) private
- #enabled_line?(line_number) ⇒ Boolean private
- #file_name_matches_any?(file, parameter, default_result) ⇒ Boolean private
- #find_message(range, message) private
- #find_severity(_range, severity) private
- #range_for_original(range) private
- #range_from_node_or_range(node_or_range) private
- #reset_investigation private
- #use_corrector(range, corrector) ⇒ Symbol private
AutocorrectLogic
- Included
#disable_offense, #disable_offense_at_end_of_line, #disable_offense_before_and_after, #disable_offense_with_eol_or_surround_comment, #max_line_length, | |
#range_by_lines | Expand the given range to include all of any lines it covers. |
#range_of_first_line, #surrounding_heredoc, #surrounding_percent_array |
IgnoredNode
- Included
Constructor Details
.new(config = nil, options = nil) ⇒ Base
# File 'lib/rubocop/cop/base.rb', line 153
def initialize(config = nil, = nil) @config = config || Config.new @options = || { debug: false } reset_investigation end
Class Attribute Details
.builtin? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'lib/rubocop/cop/base.rb', line 402
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
.gem_requirements (readonly)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 135
attr_reader :gem_requirements
.lint? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/rubocop/cop/base.rb', line 102
def self.lint? department == :Lint end
.support_autocorrect? ⇒ Boolean
(readonly)
Returns if class supports autocorrect. It is recommended to extend AutoCorrector instead of overriding
# File 'lib/rubocop/cop/base.rb', line 84
def self.support_autocorrect? false end
.support_multiple_source? ⇒ Boolean
(readonly)
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 126
def self.support_multiple_source? false end
Class Method Details
.autocorrect_incompatible_with ⇒ Array
<Base
>
List of cops that should not try to autocorrect at the same time as this cop
# File 'lib/rubocop/cop/base.rb', line 59
def self.autocorrect_incompatible_with [] end
.badge
Naming
.callbacks_needed
# File 'lib/rubocop/cop/base.rb', line 316
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
.cop_name
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 94
def self.cop_name badge.to_s end
.department
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 98
def self.department badge.department end
.documentation_url ⇒ String?
Cops (other than builtin) are encouraged to implement this
# File 'lib/rubocop/cop/base.rb', line 67
def self.documentation_url Documentation.url_for(self) if builtin? end
.exclude_from_registry
Call for abstract Cop classes
.inherited(subclass)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 71
def self.inherited(subclass) super subclass.instance_variable_set(:@gem_requirements, gem_requirements.dup) Registry.global.enlist(subclass) end
.joining_forces
Override and return the Force class(es) you need to join
# File 'lib/rubocop/cop/base.rb', line 115
def self.joining_forces; end
.match?(given_names) ⇒ Boolean
Returns true if the cop name or the cop namespace matches any of the given names.
.requires_gem(gem_name, *version_requirements)
Register a version requirement for the given gem name. This cop will be skipped unless the target satisfies all requirements.
# File 'lib/rubocop/cop/base.rb', line 148
def requires_gem(gem_name, *version_requirements) @gem_requirements[gem_name] = Gem::Requirement.new(version_requirements) end
.restrict_on_send (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 384
private_class_method def self.restrict_on_send @restrict_on_send ||= self::RESTRICT_ON_SEND.to_a.freeze end
Instance Attribute Details
#active_support_extensions_enabled? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/rubocop/cop/base.rb', line 269
def active_support_extensions_enabled? @config.active_support_extensions_enabled? end
#always_autocorrect? ⇒ Boolean
(readonly)
# File 'lib/rubocop/cop/base.rb', line 340
def always_autocorrect? # `true` is the same as `'always'` for backward compatibility. ['always', true].include?(cop_config.fetch('AutoCorrect', 'always')) end
#config (readonly)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 43
attr_reader :config, :processed_source
#config_to_allow_offenses (rw)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 249
def config_to_allow_offenses Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] ||= {} end
#config_to_allow_offenses=(hash) (rw)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 253
def config_to_allow_offenses=(hash) Formatter::DisabledConfigFormatter.config_to_allow_offenses[cop_name] = hash end
#contextual_autocorrect? ⇒ Boolean
(readonly)
# File 'lib/rubocop/cop/base.rb', line 346
def contextual_autocorrect? cop_config.fetch('AutoCorrect', 'always') == 'contextual' end
#processed_source (readonly)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 43
attr_reader :config, :processed_source
#target_satisfies_all_gem_version_requirements? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'lib/rubocop/cop/base.rb', line 527
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
Instance Method Details
#add_global_offense(message = nil, severity: nil)
Adds an offense that has no particular location. No correction can be applied to global offenses
# File 'lib/rubocop/cop/base.rb', line 186
def add_global_offense( = nil, severity: nil) severity = find_severity(nil, severity) = (nil, ) range = Offense::NO_LOCATION status = enabled_line?(range.line) ? :unsupported : :disabled current_offenses << Offense.new(severity, range, , name, status) end
#add_offense(node_or_range, message: nil, severity: nil, &block)
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 198
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) = (range_to_pass, ) 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, , name, status, corrector) end
#annotate(message) (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 478
def annotate( ) RuboCop::Cop::MessageAnnotator.new( config, cop_name, cop_config, @options ).annotate( ) end
#apply_correction(corrector) (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 362
def apply_correction(corrector) current_corrector&.merge!(corrector) if corrector end
#attempt_correction(range, corrector) ⇒ Symbol
(private)
# File 'lib/rubocop/cop/base.rb', line 442
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, offset: 0, original: processed_source)
Called before any investigation
# File 'lib/rubocop/cop/base.rb', line 326
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
#callback_argument(range) (private)
Reserved for Cop::Cop
# File 'lib/rubocop/cop/base.rb', line 358
def callback_argument(range) range end
#callbacks_needed
# File 'lib/rubocop/cop/base.rb', line 311
def callbacks_needed self.class.callbacks_needed end
#complete_investigation (private)
Called to complete an investigation
# File 'lib/rubocop/cop/base.rb', line 391
def complete_investigation InvestigationReport.new( self, processed_source, @current_offenses || EMPTY_OFFENSES, @current_corrector ) ensure reset_investigation end
#cop_config
Configuration Helpers
# File 'lib/rubocop/cop/base.rb', line 243
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 Also known as: #name
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 235
def cop_name @cop_name ||= self.class.cop_name end
#correct(range) ⇒ Symbol
, Corrector (private)
# File 'lib/rubocop/cop/base.rb', line 416
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 (private)
[ GitHub ]#current_offense_locations (private)
Reserved for Commissioner:
# File 'lib/rubocop/cop/base.rb', line 368
def current_offense_locations @current_offense_locations ||= Set.new end
#current_offenses (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 380
def current_offenses @current_offenses ||= [] end
#currently_disabled_lines (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 372
def currently_disabled_lines @currently_disabled_lines ||= Set.new end
#custom_severity (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 506
def custom_severity severity = cop_config['Severity'] return unless severity if Severity::NAMES.include?(severity.to_sym) severity.to_sym else = "Warning: Invalid severity '#{severity}'. " \ "Valid severities are #{Severity::NAMES.join(', ')}." warn(Rainbow( ).red) end end
#default_severity (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 502
def default_severity self.class.lint? ? :warning : :convention end
#disable_uncorrectable(range) (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 456
def disable_uncorrectable(range) line = range.line return unless currently_disabled_lines.add?(line) disable_offense(range) end
#enabled_line?(line_number) ⇒ Boolean
(private)
# File 'lib/rubocop/cop/base.rb', line 492
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
#excluded_file?(file) ⇒ Boolean
# File 'lib/rubocop/cop/base.rb', line 282
def excluded_file?(file) !relevant_file?(file) end
#external_dependency_checksum
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 231
def external_dependency_checksum nil end
#file_name_matches_any?(file, parameter, default_result) ⇒ Boolean
(private)
# File 'lib/rubocop/cop/base.rb', line 484
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) (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 474
def (range, ) annotate( || (range)) end
#find_severity(_range, severity) (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 498
def find_severity(_range, severity) custom_severity || severity || default_severity end
#inspect
# File 'lib/rubocop/cop/base.rb', line 350
def inspect # :nodoc: "#<#{self.class.name}:#{object_id} @config=#{@config} @options=#{@options}>" end
#message(_range = nil)
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 180
def (_range = nil) self.class::MSG end
#name
Alias for #cop_name.
# File 'lib/rubocop/cop/base.rb', line 239
alias name cop_name
#offenses
Make potential errors with previous API more obvious
# File 'lib/rubocop/cop/base.rb', line 302
def offenses raise 'The offenses are not directly available; ' \ 'they are returned as the result of the investigation' end
#on_investigation_end
Called after all on_… have been called
When refining this method, always call super
# File 'lib/rubocop/cop/base.rb', line 167
def on_investigation_end # Typically do nothing here end
#on_new_investigation
Called before all on_… have been called
When refining this method, always call super
# File 'lib/rubocop/cop/base.rb', line 161
def on_new_investigation # Typically do nothing here end
#on_other_file
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 173
def on_other_file # Typically do nothing here end
#parse(source, path = nil)
There should be very limited reasons for a Cop to do it’s own parsing
# File 'lib/rubocop/cop/base.rb', line 287
def parse(source, path = nil) ProcessedSource.new(source, target_ruby_version, path, parser_engine: parser_engine) end
#parser_engine
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 261
def parser_engine @config.parser_engine end
#range_for_original(range) (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 519
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) (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 463
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
#ready
Called between investigations
# File 'lib/rubocop/cop/base.rb', line 293
def ready return self if self.class.support_multiple_source? self.class.new(@config, @options) end
#relevant_file?(file) ⇒ Boolean
# File 'lib/rubocop/cop/base.rb', line 273
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
#reset_investigation (private)
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 411
def reset_investigation @currently_disabled_lines = @current_offenses = @processed_source = @current_corrector = nil end
#target_rails_version
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 265
def target_rails_version @config.target_rails_version end
#target_ruby_version
[ GitHub ]# File 'lib/rubocop/cop/base.rb', line 257
def target_ruby_version @config.target_ruby_version end
#use_corrector(range, corrector) ⇒ Symbol
(private)
# File 'lib/rubocop/cop/base.rb', line 431
def use_corrector(range, corrector) if autocorrect? attempt_correction(range, corrector) elsif corrector && (always_autocorrect? || (contextual_autocorrect? && !LSP.enabled?)) :uncorrected else :unsupported end end