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:
self, DisableNext
Inherits: Object
Defined in: lib/rubocop/comment_config.rb,
lib/rubocop/comment_config/directive_range.rb,
lib/rubocop/comment_config/disable_next.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

DisableNext - Included

#detached_next_directives

disable-next directives that suppress 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, #attached_code_line, #heredoc?, #statement_bounds_at, #statement_end_line, #statement_start_line?, #statement_starting_at

Constructor Details

.new(processed_source) ⇒ CommentConfig

[ GitHub ]

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

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 29

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 116

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)
    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 198

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 218

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 222

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 209

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

#apply_cop_op(analyses, operation, cop, directive) (private)

[ GitHub ]

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

def apply_cop_op(analyses, operation, cop, directive)
  analysis = analyses[cop]
  line = directive.line_number
  if operation == '-' && !analysis.start_line_number
    analyses[cop] = CopAnalysis.new(analysis.line_ranges, line, directive)
  elsif operation == '+' && analysis.start_line_number
    analyses[cop] = CopAnalysis.new(analysis.close(line), nil)
  end
end

#apply_push(analyses, resolved_cops, directive) (private)

[ GitHub ]

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

def apply_push(analyses, resolved_cops, directive)
  resolved_cops.each do |op, cops|
    cops.each { |cop| apply_cop_op(analyses, op, cop, directive) }
  end
end

#comment_only_line?(line_number) ⇒ Boolean

[ GitHub ]

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

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 62

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 40

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 48

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 226

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

#cop_opted_in?(cop) ⇒ Boolean

[ GitHub ]

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

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

#each_directive (private)

[ GitHub ]

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

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

#expand_cop_name(name) (private)

[ GitHub ]

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

def expand_cop_name(name)
  registry = Cop::Registry.global
  cops = registry.department?(name) ? registry.names_for_department(name) : [name]
  cops.map { |c| qualified_cop_name(c) }
end

#extra_enabled_comments

[ GitHub ]

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

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)
    # Push/pop and next-statement directives close themselves, so they
    # play no part in the disable/enable pairing.
    next if directive.push? || directive.pop? || directive.disable_next?

    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 264

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 278

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 189

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 257

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, used to mobilize cops disabled in the config on demand.

[ GitHub ]

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

def opt_in_cops
  @opt_in_cops ||= begin
    cops = Set.new
    each_directive do |directive|
      next unless directive.enabled?
      next if directive.all_cops?

      cops.merge(directive.raw_cop_names)
    end
    cops
  end
end

#pop_state(analyses, line) (private)

[ GitHub ]

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

def pop_state(analyses, line)
  restore_point = @stack.pop
  (restore_point.keys | analyses.keys).each do |cop|
    analyses[cop] = popped_analysis(analyses[cop], restore_point[cop], line)
  end
end

#popped_analysis(current, restored, line) (private)

[ GitHub ]

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

def popped_analysis(current, restored, line)
  ranges = current.close(line - 1)
  return CopAnalysis.new(ranges, nil) unless restored&.start_line_number

  CopAnalysis.new(ranges, line, restored.start_directive)
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 252

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 239

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

#resolve_push_cops(directive) (private)

[ GitHub ]

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

def resolve_push_cops(directive)
  directive.push_args.transform_values do |names|
    names.flat_map { |name| expand_cop_name(name) }
  end
end