class RuboCop::Cop::Lint::RedundantCopDisableDirective

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.

@example

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

# good
x += 1

Constants

COP_NAME
DEPARTMENT_MARKER
SIMILAR_COP_NAMES_CACHE

Attributes

offenses_to_check[RW]

Public Class Methods

new(config = nil, options = nil, offenses = nil) click to toggle source
Calls superclass method RuboCop::Cop::Base::new
# 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

Public Instance Methods

on_new_investigation() click to toggle source
Calls superclass method RuboCop::Cop::Base#on_new_investigation
# 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

Private Instance Methods

add_department_marker(department) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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() click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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() click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/rubocop/cop/lint/redundant_cop_disable_directive.rb, line 315
def department_marker?(department)
  department.start_with?(DEPARTMENT_MARKER)
end
describe(cop) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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() click to toggle source
# 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) { |comment, redundant| ... } click to toggle source

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# 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) { |comment, redundant| ... } click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/rubocop/cop/lint/redundant_cop_disable_directive.rb, line 302
def message(cop_names)
  "Unnecessary disabling of #{cop_names}."
end
previous_line_blank?(range) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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