123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Lint::RedundantCopDisableDirective

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::AutoCorrector, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/lint/redundant_cop_disable_directive.rb

Overview

Detects instances of rubocop:disable comments that can be removed without causing any offenses to be reported. It’s implemented as a cop in that it inherits from the Cop base class and calls add_offense. The unusual part of its implementation is that it doesn’t have any on_* methods or an investigate method. This means that it doesn’t take part in the investigation phase when the other cops do their work. Instead, it waits until it’s called in a later stage of the execution. The reason it can’t be implemented as a normal cop is that it depends on the results of all other cops to do its work.

Examples:

# bad
# rubocop:disable Layout/LineLength
x += 1
# rubocop:enable Layout/LineLength

# good
x += 1

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::RangeHelp - Included

BYTE_ORDER_MARK, NOT_GIVEN

Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_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.

.builtin?

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

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

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Cops (other than builtin) are encouraged to implement this.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

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

.match?

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

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::RangeHelp - Included

#add_range, #column_offset_between,
#contents_range

A range containing only the contents of a literal with delimiters (e.g.

#directions,
#effective_column

Returns the column attribute of the range, except if the range is on the first line and there’s a byte order mark at the beginning of that line, in which case 1 is subtracted from the column value.

#final_pos, #move_pos, #move_pos_str, #range_between, #range_by_whole_lines, #range_with_comments, #range_with_comments_and_lines, #range_with_surrounding_comma, #range_with_surrounding_space, #source_range

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

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

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

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

#inspect,
#message

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.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

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

#parser_engine,
#ready

Called between investigations.

#relevant_file?, #target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range, #reset_investigation, #use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

.new(config = nil, options = nil, offenses = nil) ⇒ RedundantCopDisableDirective

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 37

def initialize(config = nil, options = nil, offenses = nil)
  @offenses_to_check = offenses
  super(config, options)
end

Instance Attribute Details

#offenses_to_check (rw)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 35

attr_accessor :offenses_to_check

Instance Method Details

#add_department_marker(department) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 323

def add_department_marker(department)
  DEPARTMENT_MARKER + department
end

#add_offense_for_entire_comment(comment, cops) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 229

def add_offense_for_entire_comment(comment, cops)
  location = DirectiveComment.new(comment).range
  cop_names = cops.sort.map { |c| describe(c) }.join(', ')

  add_offense(location, message: message(cop_names)) do |corrector|
    range = comment_range_with_surrounding_space(location, comment.source_range)

    if leave_free_comment?(comment, range)
      corrector.replace(range, ' # ')
    else
      corrector.remove(range)
    end
  end
end

#add_offense_for_some_cops(comment, cops) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 244

def add_offense_for_some_cops(comment, cops)
  cop_ranges = cops.map { |c| [c, cop_range(comment, c)] }
  cop_ranges.sort_by! { |_, r| r.begin_pos }
  ranges = cop_ranges.map { |_, r| r }

  cop_ranges.each do |cop, range|
    cop_name = describe(cop)
    add_offense(range, message: message(cop_name)) do |corrector|
      range = directive_range_in_list(range, ranges)
      corrector.remove(range)
    end
  end
end

#add_offenses(redundant_cops) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 219

def add_offenses(redundant_cops)
  redundant_cops.each do |comment, cops|
    if all_disabled?(comment) || directive_count(comment) == cops.size
      add_offense_for_entire_comment(comment, cops)
    else
      add_offense_for_some_cops(comment, cops)
    end
  end
end

#all_cop_names (private)

[ GitHub ]

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

def all_cop_names
  @all_cop_names ||= Registry.global.names
end

#all_disabled?(comment) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 190

def all_disabled?(comment)
  DirectiveComment.new(comment).disabled_all?
end

#comment_range_with_surrounding_space(directive_comment_range, line_comment_range) (private)

[ GitHub ]

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

def comment_range_with_surrounding_space(directive_comment_range, line_comment_range)
  if previous_line_blank?(directive_comment_range) &&
     processed_source.comment_config.comment_only_line?(directive_comment_range.line) &&
     directive_comment_range.begin_pos == line_comment_range.begin_pos
    # When the previous line is blank, it should be retained
    range_with_surrounding_space(directive_comment_range, side: :right)
  else
    # Eat the entire comment, the preceding space, and the preceding
    # newline if there is one.
    original_begin = directive_comment_range.begin_pos
    range = range_with_surrounding_space(
      directive_comment_range, side: :left, newlines: true
    )

    range_with_surrounding_space(range,
                                 side: :right,
                                 # Special for a comment that
                                 # begins the file: remove
                                 # the newline at the end.
                                 newlines: original_begin.zero?)
  end
end

#cop_disabled_line_ranges (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 57

def cop_disabled_line_ranges
  processed_source.disabled_line_ranges
end

#cop_range(comment, cop) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 264

def cop_range(comment, cop)
  cop = remove_department_marker(cop)
  matching_range(comment.source_range, cop) ||
    matching_range(comment.source_range, Badge.parse(cop).cop_name) ||
    raise("Couldn't find #{cop} in comment: #{comment.text}")
end

#department_disabled?(cop, comment) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 210

def department_disabled?(cop, comment)
  directive = DirectiveComment.new(comment)
  directive.in_directive_department?(cop) && !directive.overridden_by_department?(cop)
end

#department_marker?(department) ⇒ Boolean (private)

[ GitHub ]

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

def department_marker?(department)
  department.start_with?(DEPARTMENT_MARKER)
end

#describe(cop) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 293

def describe(cop)
  return 'all cops' if cop == 'all'
  return "`#{remove_department_marker(cop)}` department" if department_marker?(cop)
  return "`#{cop}`" if all_cop_names.include?(cop)

  similar = SIMILAR_COP_NAMES_CACHE[cop]
  similar ? "`#{cop}` (did you mean `#{similar}`?)" : "`#{cop}` (unknown cop)"
end

#directive_count(comment) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 215

def directive_count(comment)
  DirectiveComment.new(comment).directive_count
end

#directive_range_in_list(range, ranges) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 92

def directive_range_in_list(range, ranges)
  # Is there any cop between this one and the end of the line, which
  # is NOT being removed?
  if ends_its_line?(ranges.last) && trailing_range?(ranges, range)
    # Eat the comma on the left.
    range = range_with_surrounding_space(range, side: :left)
    range = range_with_surrounding_comma(range, :left)
  end

  range = range_with_surrounding_comma(range, :right)
  # Eat following spaces up to EOL, but not the newline itself.
  range_with_surrounding_space(range, side: :right, newlines: false)
end

#disabled_ranges (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 61

def disabled_ranges
  cop_disabled_line_ranges[COP_NAME] || [0..0]
end

#each_already_disabled(cop, line_ranges) (private)

Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 132

def each_already_disabled(cop, line_ranges)
  line_ranges.each_cons(2) do |previous_range, range|
    next if ignore_offense?(range)
    # If a cop is disabled in a range that begins on the same line as
    # the end of the previous range, it means that the cop was
    # already disabled by an earlier comment. So it's redundant
    # whether there are offenses or not.
    next unless followed_ranges?(previous_range, range)

    comment = processed_source.comment_at_line(range.begin)

    next unless comment
    # Comments disabling all cops don't count since it's reasonable
    # to disable a few select cops first and then all cops further
    # down in the code.
    next if all_disabled?(comment)

    redundant =
      if department_disabled?(cop, comment)
        find_redundant_department(cop, range)
      else
        cop
      end

    yield comment, redundant if redundant
  end
end

#each_line_range(cop, line_ranges) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 113

def each_line_range(cop, line_ranges)
  line_ranges.each_with_index do |line_range, line_range_index|
    next if ignore_offense?(line_range)
    next if expected_final_disable?(cop, line_range)

    comment = processed_source.comment_at_line(line_range.begin)
    redundant = if all_disabled?(comment)
                  find_redundant_all(line_range, line_ranges[line_range_index + 1])
                elsif department_disabled?(cop, comment)
                  find_redundant_department(cop, line_range)
                else
                  find_redundant_cop(cop, line_range)
                end

    yield comment, redundant if redundant
  end
end

#each_redundant_disable(&block) (private)

[ GitHub ]

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

def each_redundant_disable(&block)
  cop_disabled_line_ranges.each do |cop, line_ranges|
    each_already_disabled(cop, line_ranges, &block)
    each_line_range(cop, line_ranges, &block)
  end
end

#ends_its_line?(range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 310

def ends_its_line?(range)
  line = range.source_buffer.source_line(range.last_line)
  (line =~ /\s*\z/) == range.last_column
end

#expected_final_disable?(cop, line_range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 202

def expected_final_disable?(cop, line_range)
  # A cop which is disabled in the config is being re-disabled until end of file
  cop_class = RuboCop::Cop::Registry.global.find_by_cop_name cop
  cop_class &&
    !processed_source.registry.enabled?(cop_class, config) &&
    line_range.max == Float::INFINITY
end

#find_redundant_all(range, next_range) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 166

def find_redundant_all(range, next_range)
  # If there's a disable all comment followed by a comment
  # specifically disabling `cop`, we don't report the `all`
  # comment. If the disable all comment is truly redundant, we will
  # detect that when examining the comments of another cop, and we
  # get the full line range for the disable all.
  has_no_next_range = next_range.nil? || !followed_ranges?(range, next_range)
  'all' if has_no_next_range && range_with_offense?(range)
end

#find_redundant_cop(cop, range) (private)

Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 161

def find_redundant_cop(cop, range)
  cop_offenses = offenses_to_check.select { |offense| offense.cop_name == cop }
  cop if range_with_offense?(range, cop_offenses)
end

#find_redundant_department(cop, range) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 176

def find_redundant_department(cop, range)
  department = cop.split('/').first
  offenses = offenses_to_check.select { |offense| offense.cop_name.start_with?(department) }
  add_department_marker(department) if range_with_offense?(range, offenses)
end

#followed_ranges?(range, next_range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 182

def followed_ranges?(range, next_range)
  range.end == next_range.begin
end

#ignore_offense?(line_range) ⇒ Boolean (private)

[ GitHub ]

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

def ignore_offense?(line_range)
  return true if line_range.min == CommentConfig::CONFIG_DISABLED_LINE_RANGE_MIN

  disabled_ranges.any? do |range|
    range.cover?(line_range.min) && range.cover?(line_range.max)
  end
end

#leave_free_comment?(comment, range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 258

def leave_free_comment?(comment, range)
  free_comment = comment.text.gsub(range.source.strip, '')

  !free_comment.empty? && !free_comment.start_with?('#')
end

#matching_range(haystack, needle) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 271

def matching_range(haystack, needle)
  offset = haystack.source.index(needle)
  return unless offset

  offset += haystack.begin_pos
  Parser::Source::Range.new(haystack.source_buffer, offset, offset + needle.size)
end

#message(cop_names) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 302

def message(cop_names)
  "Unnecessary disabling of #{cop_names}."
end

#on_new_investigation

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 42

def on_new_investigation
  return unless offenses_to_check

  redundant_cops = Hash.new { |h, k| h[k] = Set.new }

  each_redundant_disable do |comment, redundant_cop|
    redundant_cops[comment].add(redundant_cop)
  end

  add_offenses(redundant_cops)
  super
end

#previous_line_blank?(range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 65

def previous_line_blank?(range)
  processed_source.buffer.source_line(range.line - 1).blank?
end

#range_with_offense?(range, offenses = offenses_to_check) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 186

def range_with_offense?(range, offenses = offenses_to_check)
  offenses.none? { |offense| range.cover?(offense.line) }
end

#remove_department_marker(department) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 319

def remove_department_marker(department)
  department.gsub(DEPARTMENT_MARKER, '')
end

#trailing_range?(ranges, range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/redundant_cop_disable_directive.rb', line 279

def trailing_range?(ranges, range)
  ranges
    .drop_while { |r| !r.equal?(range) }
    .each_cons(2)
    .map { |range1, range2| range1.end.join(range2.begin).source }
    .all?(/\A\s*,\s*\z/)
end