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 126

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 170

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 190

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 194

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 181

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 198

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 202

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 98

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 243

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 257

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 161

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 236

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_cops ⇒ Set<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. Departments are expanded for the opt-in forms only: a plain enable naming a department usually just closes a disable of it, and expanding it would switch on every config-disabled cop of the department for the rest of the file.

[ GitHub ]

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

def opt_in_cops
  @opt_in_cops ||= begin
    cops = Set.new
    each_directive { |directive| cops.merge(opted_in_by(directive)) }
    cops
  end
end

#opted_in_by(directive) (private)

[ GitHub ]

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

def opted_in_by(directive)
  if directive.push? || directive.next?
    directive.signed_args.fetch('+', []).flat_map { |name| expand_cop_name(name) }
  elsif !directive.enabled? || directive.all_cops?
    []
  elsif directive.enable_next?
    directive.cop_names
  else
    directive.raw_cop_names
  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 231

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 218

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 213

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