123456789_123456789_123456789_123456789_123456789_

Class: SimpleCov::Directive

Relationships & Source Files
Inherits: Object
Defined in: lib/simplecov/directive.rb

Overview

Parses # simplecov:disable / # simplecov:enable directive comments.

Two forms are supported:

Block form (the directive is the entire comment on its own line) opens a region that runs until the matching # simplecov:enable:

# simplecov:disable line
#...
# simplecov:enable line

Inline form (the directive trails real code on the same line) only affects that single line and does not need to be re-enabled:

raise "absurd" # simplecov:disable

Categories are :line, :branch, and :method. They may be combined with commas. Omitting categories targets all three.

Any text after the directive (and the optional category list) is treated as a free-form reason and discarded:

# simplecov:disable line not worth testing this glue

As a consequence, an unrecognised category name silently falls into the reason bucket. # simplecov:disable cyclomatic is parsed as the bare form (disable everything) with reason "cyclomatic" — a deliberate over-disable so the typo is visible in the report rather than silently disabling nothing.

Comment extraction goes through Ripper.lex so directive markers inside string literals or heredocs are correctly ignored.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(line_number:, mode:, categories:, inline:) ⇒ Directive

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 132

def initialize(line_number:, mode:, categories:, inline:)
  @line_number = line_number
  @mode        = mode
  @categories  = categories
  @inline      = inline
end

Class Method Details

.comments_in(lines) (private)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 120

def self.comments_in(lines)
  source = lines.map { |line| line.end_with?("\n") ? line : "#{line}\n" }.join
  Ripper.lex(source).filter_map do |(line_number, column), type, text|
    [line_number, column, text] if type == :on_comment
  end
rescue ArgumentError, EncodingError
  [] # simplecov:disable — Ripper.lex can raise on invalid byte sequences
end

.directives_in(lines) (private)

Extract every directive in the file, in source order. Comments inside string literals or heredocs are skipped because Ripper.lex doesn't tag them as :on_comment tokens.

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 70

def self.directives_in(lines)
  return [] unless source_might_contain_directive?(lines)

  comments_in(lines).filter_map do |line_number, column, text|
    parse_comment(lines, line_number, column, text)
  end
end

.disabled_ranges(src_lines)

Walk an array of source lines and return the disabled line ranges per category as { line: [Range, ...], branch: [...], method: [...] }. An unclosed disable block extends to the end of the file.

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 56

def self.disabled_ranges(src_lines)
  lines = src_lines.to_a
  ranges = CATEGORIES.to_h { |category| [category, []] }
  open_starts = {}

  directives_in(lines).each { |directive| directive.apply(ranges, open_starts) }
  open_starts.each { |category, start| ranges[category] << (start..lines.size) }

  ranges
end

.inline?(lines, line_number, column) ⇒ Boolean (private)

Whether the directive sits after non-whitespace content on its line. column is the byte column of the directive's # in the source line, adjusted for any prefix that may precede it within the comment token (e.g., # prefix # simplecov:disable line).

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 113

def self.inline?(lines, line_number, column)
  line = lines[line_number - 1].to_s
  !line.byteslice(0, column).to_s.strip.empty?
rescue ArgumentError, EncodingError
  false # simplecov:disable — defensive guard for invalid byte sequences
end

.parse_categories(text) (private)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 103

def self.parse_categories(text)
  return CATEGORIES.dup if text.nil?

  text.split(/\s*,\s*/).map(&:to_sym)
end

.parse_comment(lines, line_number, column, text) (private)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 88

def self.parse_comment(lines, line_number, column, text)
  match = PATTERN.match(text)
  return nil unless match

  new(
    line_number: line_number,
    mode: match[:mode].to_sym,
    categories: parse_categories(match[:categories]),
    inline: inline?(lines, line_number, column + match.begin(0))
  )
rescue ArgumentError, EncodingError
  # E.g., comment text contains an invalid byte sequence in UTF-8.
  nil
end

.source_might_contain_directive?(lines) ⇒ Boolean (private)

Cheap pre-check so we don't tokenize files that obviously can't contain a directive.

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 80

def self.source_might_contain_directive?(lines)
  lines.any? do |line|
    line.include?("simplecov")
  rescue ArgumentError, EncodingError
    false # simplecov:disable — defensive guard for invalid byte sequences in source
  end
end

Instance Attribute Details

#categories (readonly)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 51

attr_reader :line_number, :mode, :categories

#disabled?Boolean (readonly)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 139

def disabled?
  mode == :disable
end

#inline?Boolean (readonly)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 143

def inline?
  @inline
end

#line_number (readonly)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 51

attr_reader :line_number, :mode, :categories

#mode (readonly)

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 51

attr_reader :line_number, :mode, :categories

Instance Method Details

#apply(ranges, open_starts)

Apply this directive's effect to the in-flight per-category state. Inline directives mark just their line; block disables open a region; block enables close one. Re-opening an already-open block is a no-op.

[ GitHub ]

  
# File 'lib/simplecov/directive.rb', line 150

def apply(ranges, open_starts)
  categories.each do |category|
    if inline?
      ranges[category] << (line_number..line_number) if disabled?
    elsif disabled?
      open_starts[category] ||= line_number
    elsif (start = open_starts.delete(category))
      ranges[category] << (start..line_number)
    end
  end
end