123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::CommentConfig

Relationships & Source Files
Namespace Children
Modules:
Classes:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, SimpleForwardable
Instance Chain:
Inherits: Object
Defined in: lib/rubocop/comment_config.rb,
lib/rubocop/comment_config/directive_range.rb,
lib/rubocop/comment_config/disable_next.rb,
lib/rubocop/comment_config/push_pop.rb

Overview

This class parses the special rubocop:disable comments in a source and provides a way to check if each cop is enabled at arbitrary line.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

PushPop - Included

DisableNext - Included

#detached_next_directives

Next-statement directives that affect nothing: nothing follows them, or they sit at the end of a code line.

#statement_scope_after

The line range of the statement a disable-next directive on the given line scopes to (or would scope to), or nil when no statement is attached.

#add_next_range, #apply_disable_next,
#apply_enable_next

Applies an enable-next directive: every listed cop (departments and all included) has any open disable suspended for the attached statement.

#apply_next_directive

Applies a next directive: - cops are disabled for the attached statement (exactly like disable-next), + cops have any open disable suspended for it.

#attached_code_line, #heredoc?, #statement_bounds_at, #statement_end_line, #statement_start_line?, #statement_starting_at,
#suspend_disable

Punches a statement-sized hole into the currently open disable of the cop (whether opened by a directive or injected for a config-disabled cop): the open range closes just above the statement and reopens right below it, still attributed to its opening directive.

Constructor Details

.new(processed_source) ⇒ CommentConfig

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 34

def initialize(processed_source)
  @processed_source = processed_source
  @no_directives = !processed_source.raw_source.include?('rubocop')
  @stack = []
  @detached_next_directives = []
end

Instance Attribute Details

#processed_source (readonly)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 30

attr_reader :processed_source

Instance Method Details

#analyze (private)

rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 117

def analyze
  return {} if @no_directives

  analyses = Hash.new { |hash, key| hash[key] = CopAnalysis.new([], nil) }
  inject_disabled_cops_directives(analyses)

  each_directive do |directive|
    if directive.push?
      restore_point = analyses.transform_values(&:dup)
      @stack.push(restore_point)
      apply_push(analyses, resolve_push_cops(directive), directive)
    elsif directive.pop?
      pop_state(analyses, directive.line_number) if @stack.any?
    elsif directive.disable_next?
      apply_disable_next(analyses, directive)
    elsif directive.next?
      apply_next_directive(analyses, resolve_push_cops(directive), directive)
    elsif directive.enable_next?
      apply_enable_next(analyses, directive)
    else
      directive.cop_names.each do |cop_name|
        cop_name = qualified_cop_name(cop_name)
        analyses[cop_name] = analyze_cop(analyses[cop_name], directive)
      end
    end
  end

  analyses.each_with_object({}) do |element, hash|
    cop_name, analysis = *element
    next if prevent_directive_disabling?(cop_name)

    hash[cop_name] = cop_line_ranges(analysis)
  end
end

#analyze_cop(analysis, directive) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 161

def analyze_cop(analysis, directive)
  # Disabling cops after comments like `#=SomeDslDirective` does not related to single line
  if !comment_only_line?(directive.line_number) || directive.single_line?
    analyze_single_line(analysis, directive)
  elsif directive.disabled?
    analyze_disabled(analysis, directive)
  else
    analyze_rest(analysis, directive)
  end
end

#analyze_disabled(analysis, directive) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 181

def analyze_disabled(analysis, directive)
  CopAnalysis.new(analysis.close(directive.line_number), directive.line_number, directive)
end

#analyze_rest(analysis, directive) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 185

def analyze_rest(analysis, directive)
  CopAnalysis.new(analysis.close(directive.line_number), nil)
end

#analyze_single_line(analysis, directive) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 172

def analyze_single_line(analysis, directive)
  return analysis unless directive.disabled?

  line = directive.line_number
  range = DirectiveRange.new(line, line, directive)
  CopAnalysis.new(analysis.line_ranges + [range], analysis.start_line_number,
                  analysis.start_directive)
end

#comment_only_line?(line_number) ⇒ Boolean

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 75

def comment_only_line?(line_number)
  non_comment_token_line_numbers.none?(line_number)
end

#cop_disabled_line_ranges

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 63

def cop_disabled_line_ranges
  @cop_disabled_line_ranges ||= analyze
end

#cop_enabled_at_line?(cop, line_number) ⇒ Boolean

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 41

def cop_enabled_at_line?(cop, line_number)
  cop_enabled_at_lines?(cop, line_number, line_number)
end

#cop_enabled_at_lines?(cop, first_line, last_line) ⇒ Boolean

Whether the cop is enabled for all of the given line span. The cop counts as disabled for the span when any of its disabled ranges overlaps it, so that a directive on any line of a multi-line offense suppresses the offense.

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 49

def cop_enabled_at_lines?(cop, first_line, last_line)
  cop = cop.cop_name if cop.respond_to?(:cop_name)
  disabled_line_ranges = cop_disabled_line_ranges[cop]
  return true unless disabled_line_ranges

  disabled_line_ranges.none? do |range|
    range.end >= first_line && range.begin <= last_line
  end
end

#cop_line_ranges(analysis) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 189

def cop_line_ranges(analysis)
  analysis.close(Float::INFINITY)
end

#cop_opted_in?(cop) ⇒ Boolean

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 59

def cop_opted_in?(cop)
  opt_in_cops.include?(cop.cop_name)
end

#each_directive (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 193

def each_directive
  return if @no_directives

  processed_source.comments.each do |comment|
    directive = DirectiveComment.new(comment)
    yield directive if directive.cop_names
  end
end

#extra_enabled_comments

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 67

def extra_enabled_comments
  disable_count = Hash.new(0)
  registry.disabled_names(config).each do |cop_name|
    disable_count[cop_name] += 1
  end
  extra_enabled_comments_with_names(extras: Hash.new { |h, k| h[k] = [] }, names: disable_count)
end

#extra_enabled_comments_with_names(extras:, names:) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 101

def extra_enabled_comments_with_names(extras:, names:)
  each_directive do |directive|
    next unless comment_only_line?(directive.line_number)
    next if self_closing_directive?(directive)

    if directive.enabled_all?
      handle_enable_all(directive, names, extras)
    else
      handle_switch(directive, names, extras)
    end
  end

  extras
end

#handle_enable_all(directive, names, extras) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 234

def handle_enable_all(directive, names, extras)
  enabled_cops = 0
  names.each do |name, counter|
    next unless counter.positive?

    names[name] -= 1
    enabled_cops += 1
  end

  extras[directive.comment] << 'all' if enabled_cops.zero?
end

#handle_switch(directive, names, extras) (private)

Collect cops that have been disabled or enabled by name in a directive comment so that Lint/RedundantCopEnableDirective can register offenses correctly.

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 248

def handle_switch(directive, names, extras)
  directive.cop_names.each do |name|
    if directive.disabled?
      names[name] += 1
    elsif names[name].positive?
      names[name] -= 1
    else
      extras[directive.comment] << name
    end
  end
end

#inject_disabled_cops_directives(analyses) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 152

def inject_disabled_cops_directives(analyses)
  registry.disabled_names(config).each do |cop_name|
    analyses[cop_name] = analyze_cop(
      analyses[cop_name],
      DirectiveComment.new(ConfigDisabledCopDirectiveComment.new(cop_name))
    )
  end
end

#non_comment_token_line_numbers (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 227

def non_comment_token_line_numbers
  @non_comment_token_line_numbers ||= begin
    non_comment_tokens = processed_source.tokens.reject(&:comment?)
    non_comment_tokens.map(&:line).uniq
  end
end

#opt_in_copsSet<String>

The names of the cops that are opted in by an enable comment directive or a + argument of a push/next directive, used to mobilize cops disabled in the config on demand.

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 85

def opt_in_cops
  @opt_in_cops ||= begin
    cops = Set.new
    each_directive do |directive|
      if directive.enabled?
        cops.merge(directive.raw_cop_names) unless directive.all_cops?
      elsif directive.push? || directive.next?
        cops.merge(directive.signed_args.fetch('+', []))
      end
    end
    cops
  end
end

#prevent_directive_disabling?(cop_name) ⇒ Boolean (private)

Style/DisableCopsWithinSourceCodeDirective cannot be disabled via directive comments when it is explicitly enabled with Enabled: true. This prevents users from bypassing the cop by writing a disable directive that targets this cop itself.

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 222

def prevent_directive_disabling?(cop_name)
  cop_name == DirectiveComment::STYLE_DISABLE_COPS_DIRECTIVE_COP &&
    config.dig(cop_name, 'Enabled') == true
end

#qualified_cop_name(cop_name) (private)

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 209

def qualified_cop_name(cop_name)
  # A directive naming a cop under the wrong department is not honored -
  # the mistake is surfaced by `Lint/RedundantCopDisableDirective` (with a
  # did-you-mean hint) instead of a stderr warning that is easy to miss
  # and disappears entirely on cached runs.
  Cop::Registry.qualified_cop_name(cop_name.strip, processed_source.file_path,
                                   correct_namespace: false)
end

#self_closing_directive?(directive) ⇒ Boolean (private)

Push/pop and next-statement directives close themselves, so they play no part in the disable/enable pairing.

[ GitHub ]

  
# File 'lib/rubocop/comment_config.rb', line 204

def self_closing_directive?(directive)
  directive.push? || directive.pop? || directive.disable_next? ||
    directive.next? || directive.enable_next?
end