lib/simplecov/source_file/skip_chunks.rb



# frozen_string_literal: true

module SimpleCov
  class SourceFile
    # Computes the set of line ranges that should be excluded from a
    # SourceFile's coverage for each criterion. Two sources contribute:
    #
    # * The deprecated `# :nocov:` block toggle (lines wrapped between
    #   even-numbered pairs of nocov markers are excluded from line and
    #   branch coverage).
    # * `# simplecov:disable` / `# simplecov:enable` block directives,
    #   which can be scoped per-criterion (`# simplecov:disable branch`,
    #   etc.) — see `SimpleCov::Directive`.
    class SkipChunks
      @nocov_warned = Set.new
      class << self
        attr_reader :nocov_warned
      end

      def initialize(filename, src)
        @filename = filename
        @src = src
      end

      # `:method` ignores nocov chunks (Ruby's Coverage doesn't tie
      # method entries to line ranges); `:line` / `:branch` honor both
      # the nocov chunks and the per-criterion directive ranges.
      def for(criterion)
        if criterion == :method
          directive_chunks.fetch(:method)
        else
          nocov_chunks + directive_chunks.fetch(criterion)
        end
      end

      # no_cov_chunks is zero indexed to work directly with the array
      # holding the lines.
      def nocov_chunks
        @nocov_chunks ||= build_nocov_chunks
      end

      def directive_chunks
        @directive_chunks ||= Directive.disabled_ranges(@src)
      end

    private

      def build_nocov_chunks
        no_cov_lines = @src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) }

        warn_nocov_deprecation(no_cov_lines.first.last) if no_cov_lines.any?

        # If we have an uneven number of nocovs we assume they go to the
        # end of the file, the source doesn't really matter. Can't deal
        # with this within the each_slice due to differing behavior in
        # JRuby: jruby/jruby#6048
        no_cov_lines << ["", @src.size] if no_cov_lines.size.odd?

        no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
          index_start..index_end
        end
      end

      # Emit a one-time-per-file deprecation warning pointing the user
      # at the `# simplecov:disable` / `# simplecov:enable` replacement.
      def warn_nocov_deprecation(first_line_number)
        return unless self.class.nocov_warned.add?(@filename)

        token = SimpleCov.current_nocov_token
        warn "#{@filename}:#{first_line_number}: [DEPRECATION] `# :#{token}:` is deprecated and will be removed " \
             "in a future release. Replace with `# simplecov:disable` / `# simplecov:enable` block comments."
      end
    end
  end
end