deep-cover 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -0
- data/.travis.yml +1 -1
- data/deep_cover.gemspec +3 -0
- data/lib/deep_cover.rb +2 -17
- data/lib/deep_cover/analyser.rb +2 -3
- data/lib/deep_cover/base.rb +3 -7
- data/lib/deep_cover/builtin_takeover.rb +2 -1
- data/lib/deep_cover/cli/deep_cover.rb +61 -62
- data/lib/deep_cover/config.rb +8 -10
- data/lib/deep_cover/core_ext/require_overrides.rb +2 -0
- data/lib/deep_cover/coverage.rb +4 -2
- data/lib/deep_cover/coverage/analysis.rb +4 -0
- data/lib/deep_cover/coverage/base.rb +2 -17
- data/lib/deep_cover/coverage/persistence.rb +2 -1
- data/lib/deep_cover/covered_code.rb +15 -3
- data/lib/deep_cover/load.rb +50 -0
- data/lib/deep_cover/memoize.rb +2 -0
- data/lib/deep_cover/node.rb +3 -1
- data/lib/deep_cover/node/root.rb +1 -1
- data/lib/deep_cover/problem_with_diagnostic.rb +1 -1
- data/lib/deep_cover/reporter.rb +3 -0
- data/lib/deep_cover/reporter/html/index.rb +24 -40
- data/lib/deep_cover/reporter/html/source.rb +2 -2
- data/lib/deep_cover/reporter/text.rb +66 -0
- data/lib/deep_cover/reporter/util/tree.rb +88 -0
- data/lib/deep_cover/tools.rb +2 -1
- data/lib/deep_cover/tools/builtin_coverage.rb +1 -2
- data/lib/deep_cover/tools/camelize.rb +7 -4
- data/lib/deep_cover/version.rb +1 -1
- metadata +19 -3
- data/lib/deep_cover/reporter/html/tree.rb +0 -55
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 3a8450fe7655e95139f1443a7e81f3c73b726e03
         | 
| 4 | 
            +
              data.tar.gz: b04f9ef3b69060ff9568cae7224bc89a845137bf
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 822e717af608dd6b6ed0010ce00f75aff18f4f60cfddc866df90ceefaf6131820a19f8a7a0e2d5eb00d961412dbcac23295e4b3aebb8be9cd1ae7ca7ecf13ab5
         | 
| 7 | 
            +
              data.tar.gz: a5849e499b6f4eb7975af2e4529f5564e99a982d0cda525e0d246e6bce698f289e24cadf9a44ba3a85c4e78ae60da41813ef443f3f7114a261a6a856cd6a024e
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -220,3 +220,18 @@ Style/StructInheritance: | |
| 220 220 |  | 
| 221 221 | 
             
            Security/YAMLLoad:
         | 
| 222 222 | 
             
              Enabled: false
         | 
| 223 | 
            +
             | 
| 224 | 
            +
            Style/ExtendSelf:
         | 
| 225 | 
            +
              Enabled: false
         | 
| 226 | 
            +
             | 
| 227 | 
            +
            Style/CommentedKeyword:
         | 
| 228 | 
            +
              Enabled: false # See https://github.com/bbatsov/rubocop/issues/5259
         | 
| 229 | 
            +
             | 
| 230 | 
            +
            Gemspec/RequiredRubyVersion:
         | 
| 231 | 
            +
              Enabled: false # See https://github.com/bbatsov/rubocop/issues/5260
         | 
| 232 | 
            +
             | 
| 233 | 
            +
            Style/MixinUsage:
         | 
| 234 | 
            +
              Enabled: false # See https://github.com/bbatsov/rubocop/issues/5261
         | 
| 235 | 
            +
             | 
| 236 | 
            +
            Style/EvalWithLocation:
         | 
| 237 | 
            +
              Enabled: false
         | 
    
        data/.travis.yml
    CHANGED
    
    
    
        data/deep_cover.gemspec
    CHANGED
    
    
    
        data/lib/deep_cover.rb
    CHANGED
    
    | @@ -1,24 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            # rubocop:disable Style/MixinUsage (See https://github.com/bbatsov/rubocop/issues/5055)
         | 
| 4 3 | 
             
            module DeepCover
         | 
| 5 | 
            -
               | 
| 6 | 
            -
              require 'parser'
         | 
| 7 | 
            -
              require 'term/ansicolor'
         | 
| 8 | 
            -
              require 'pry'
         | 
| 4 | 
            +
              require_relative 'deep_cover/load'
         | 
| 9 5 |  | 
| 10 | 
            -
               | 
| 11 | 
            -
              require_relative 'deep_cover/backports'
         | 
| 12 | 
            -
              require_relative 'deep_cover/tools'
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              # Parser
         | 
| 15 | 
            -
              silence_warnings do
         | 
| 16 | 
            -
                require 'parser/current'
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
              require_relative_dir 'deep_cover/parser_ext'
         | 
| 19 | 
            -
             | 
| 20 | 
            -
              # Main
         | 
| 21 | 
            -
              require_relative_dir 'deep_cover', except: %w[auto_run builtin_takeover]
         | 
| 6 | 
            +
              load_absolute_basics
         | 
| 22 7 |  | 
| 23 8 | 
             
              extend Base
         | 
| 24 9 | 
             
              extend Config::Setter
         | 
    
        data/lib/deep_cover/analyser.rb
    CHANGED
    
    | @@ -1,9 +1,8 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative 'node'
         | 
| 4 | 
            -
            require_relative 'covered_code'
         | 
| 5 | 
            -
             | 
| 6 3 | 
             
            module DeepCover
         | 
| 4 | 
            +
              bootstrap
         | 
| 5 | 
            +
             | 
| 7 6 | 
             
              # An analyser works on a subset of the original Node AST.
         | 
| 8 7 | 
             
              # The Root node is always considered part of the subset.
         | 
| 9 8 | 
             
              # One can iterate this subset with `each_node`, or ask
         | 
    
        data/lib/deep_cover/base.rb
    CHANGED
    
    | @@ -21,6 +21,7 @@ module DeepCover | |
| 21 21 | 
             
                end
         | 
| 22 22 |  | 
| 23 23 | 
             
                def stop
         | 
| 24 | 
            +
                  require_relative 'core_ext/require_overrides'
         | 
| 24 25 | 
             
                  AutoloadOverride.active = false if defined? AutoloadOverride
         | 
| 25 26 | 
             
                  RequireOverride.active = false
         | 
| 26 27 | 
             
                  @started = false
         | 
| @@ -72,6 +73,8 @@ module DeepCover | |
| 72 73 | 
             
                  @autoload_tracker ||= AutoloadTracker.new
         | 
| 73 74 | 
             
                end
         | 
| 74 75 |  | 
| 76 | 
            +
                private
         | 
| 77 | 
            +
             | 
| 75 78 | 
             
                def handle_relative_filename(filename)
         | 
| 76 79 | 
             
                  unless Pathname.new(filename).absolute?
         | 
| 77 80 | 
             
                    relative_to = File.dirname(caller(2..2).first.partition(/\.rb:\d/).first)
         | 
| @@ -80,12 +83,5 @@ module DeepCover | |
| 80 83 | 
             
                  filename += '.rb' unless filename =~ /\.rb$/
         | 
| 81 84 | 
             
                  filename
         | 
| 82 85 | 
             
                end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                def parser
         | 
| 85 | 
            -
                  Parser::CurrentRuby.new.tap do |parser|
         | 
| 86 | 
            -
                    parser.diagnostics.all_errors_are_fatal = true
         | 
| 87 | 
            -
                    parser.diagnostics.ignore_warnings      = true
         | 
| 88 | 
            -
                  end
         | 
| 89 | 
            -
                end
         | 
| 90 86 | 
             
              end
         | 
| 91 87 | 
             
            end
         | 
| @@ -1,7 +1,8 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require_relative '../deep_cover'
         | 
| 4 | 
            -
            require_relative ' | 
| 4 | 
            +
            require_relative 'coverage'
         | 
| 5 | 
            +
            require_relative 'core_ext/coverage_replacement'
         | 
| 5 6 |  | 
| 6 7 | 
             
            require 'coverage'
         | 
| 7 8 | 
             
            BuiltinCoverage = Coverage
         | 
| @@ -4,84 +4,83 @@ module DeepCover | |
| 4 4 | 
             
              require 'bundler/setup'
         | 
| 5 5 | 
             
              require 'slop'
         | 
| 6 6 | 
             
              require 'deep_cover'
         | 
| 7 | 
            +
              bootstrap
         | 
| 7 8 | 
             
              require_relative_dir '.'
         | 
| 8 9 |  | 
| 9 | 
            -
              module CLI
         | 
| 10 | 
            -
                 | 
| 11 | 
            -
                  extend self
         | 
| 10 | 
            +
              module CLI::DeepCover
         | 
| 11 | 
            +
                extend self
         | 
| 12 12 |  | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 13 | 
            +
                def show_version
         | 
| 14 | 
            +
                  puts "deep-cover v#{DeepCover::VERSION}; parser v#{Parser::VERSION}"
         | 
| 15 | 
            +
                end
         | 
| 16 16 |  | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 17 | 
            +
                def show_help
         | 
| 18 | 
            +
                  puts menu
         | 
| 19 | 
            +
                end
         | 
| 20 20 |  | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
                      end
         | 
| 27 | 
            -
                      delegate.public_send(method, *args, &block)
         | 
| 21 | 
            +
                class OptionParser < Struct.new(:delegate)
         | 
| 22 | 
            +
                  def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
         | 
| 23 | 
            +
                    options = args.last
         | 
| 24 | 
            +
                    if options.is_a?(Hash) && options.has_key?(:default)
         | 
| 25 | 
            +
                      args[-2] += " [#{options[:default]}]"
         | 
| 28 26 | 
             
                    end
         | 
| 27 | 
            +
                    delegate.public_send(method, *args, &block)
         | 
| 29 28 | 
             
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 30 |  | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
                    end
         | 
| 31 | 
            +
                def parse
         | 
| 32 | 
            +
                  Slop.parse do |o|
         | 
| 33 | 
            +
                    yield OptionParser.new(o)
         | 
| 35 34 | 
             
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 36 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 37 | 
            +
                def menu
         | 
| 38 | 
            +
                  @menu ||= parse do |o|
         | 
| 39 | 
            +
                    o.banner = 'usage: deep-cover [options] [path/to/app/or/gem]'
         | 
| 40 | 
            +
                    o.separator ''
         | 
| 41 | 
            +
                    o.string '-o', '--output', 'output folder', default: './coverage'
         | 
| 42 | 
            +
                    o.string '-c', '--command', 'command to run tests', default: 'bundle exec rake'
         | 
| 43 | 
            +
                    o.string '--reporter', 'reporter', default: 'html'
         | 
| 44 | 
            +
                    o.bool '--bundle', 'run bundle before the tests', default: true
         | 
| 45 | 
            +
                    o.bool '--process', 'turn off to only redo the reporting', default: true
         | 
| 46 | 
            +
                    o.bool '--open', 'open the output coverage', default: false
         | 
| 47 | 
            +
                    o.separator 'Coverage options'
         | 
| 48 | 
            +
                    @ignore_uncovered_map = Analyser::Node.optionally_covered.map do |option|
         | 
| 49 | 
            +
                      default = Config::DEFAULTS[:ignore_uncovered].include?(option)
         | 
| 50 | 
            +
                      o.bool "--ignore-#{Tools.dasherize(option)}", '', default: default
         | 
| 51 | 
            +
                      [:"ignore_#{option}", option]
         | 
| 52 | 
            +
                    end.to_h
         | 
| 53 | 
            +
                    o.separator "\nFor testing purposes:"
         | 
| 54 | 
            +
                    o.bool '--profile', 'use profiler' unless RUBY_PLATFORM == 'java'
         | 
| 55 | 
            +
                    o.string '-e', '--expression', 'test ruby expression instead of a covering a path'
         | 
| 56 | 
            +
                    o.bool '-d', '--debug', 'enter debugging after cover'
         | 
| 57 57 |  | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
                      end
         | 
| 63 | 
            -
                      o.boolean('-h', '--help')
         | 
| 58 | 
            +
                    o.separator "\nOther available commands:"
         | 
| 59 | 
            +
                    o.on('--version', 'print the version') do
         | 
| 60 | 
            +
                      show_version
         | 
| 61 | 
            +
                      exit
         | 
| 64 62 | 
             
                    end
         | 
| 63 | 
            +
                    o.boolean('-h', '--help')
         | 
| 65 64 | 
             
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 66 |  | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
                    end
         | 
| 72 | 
            -
                    options
         | 
| 67 | 
            +
                def convert_options(options)
         | 
| 68 | 
            +
                  iu = options[:ignore_uncovered] = []
         | 
| 69 | 
            +
                  @ignore_uncovered_map.each do |cli_option, option|
         | 
| 70 | 
            +
                    iu << option if options.delete(cli_option)
         | 
| 73 71 | 
             
                  end
         | 
| 72 | 
            +
                  options
         | 
| 73 | 
            +
                end
         | 
| 74 74 |  | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                    end
         | 
| 75 | 
            +
                def go
         | 
| 76 | 
            +
                  options = convert_options(menu.to_h)
         | 
| 77 | 
            +
                  if options[:help]
         | 
| 78 | 
            +
                    show_help
         | 
| 79 | 
            +
                  elsif options[:expression]
         | 
| 80 | 
            +
                    CLI::Debugger.new(options[:expression], **options).show
         | 
| 81 | 
            +
                  else
         | 
| 82 | 
            +
                    path = menu.arguments.first || '.'
         | 
| 83 | 
            +
                    CLI::InstrumentedCloneReporter.new(path, **options).run
         | 
| 85 84 | 
             
                  end
         | 
| 86 85 | 
             
                end
         | 
| 87 86 | 
             
              end
         | 
    
        data/lib/deep_cover/config.rb
    CHANGED
    
    | @@ -3,14 +3,16 @@ | |
| 3 3 | 
             
            module DeepCover
         | 
| 4 4 | 
             
              class Config
         | 
| 5 5 | 
             
                DEFAULTS = {
         | 
| 6 | 
            -
                             ignore_uncovered: [],
         | 
| 7 | 
            -
                             paths: %w[./app ./lib],
         | 
| 6 | 
            +
                             ignore_uncovered: [].freeze,
         | 
| 7 | 
            +
                             paths: %w[./app ./lib].freeze,
         | 
| 8 8 | 
             
                             allow_partial: false,
         | 
| 9 9 | 
             
                           }.freeze
         | 
| 10 10 |  | 
| 11 | 
            -
                 | 
| 11 | 
            +
                OPTIONALLY_COVERED = %i[raise default_argument case_implicit_else trivial_if]
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize(notify = nil)
         | 
| 12 14 | 
             
                  @notify = notify
         | 
| 13 | 
            -
                  @options =  | 
| 15 | 
            +
                  @options = DEFAULTS.dup
         | 
| 14 16 | 
             
                end
         | 
| 15 17 |  | 
| 16 18 | 
             
                def to_hash
         | 
| @@ -29,7 +31,7 @@ module DeepCover | |
| 29 31 |  | 
| 30 32 | 
             
                def detect_uncovered(*keywords)
         | 
| 31 33 | 
             
                  if keywords.empty?
         | 
| 32 | 
            -
                     | 
| 34 | 
            +
                    OPTIONALLY_COVERED - @options[:ignore_uncovered]
         | 
| 33 35 | 
             
                  else
         | 
| 34 36 | 
             
                    check_uncovered(keywords)
         | 
| 35 37 | 
             
                    change(:ignore_uncovered, @options[:ignore_uncovered] - keywords)
         | 
| @@ -53,7 +55,7 @@ module DeepCover | |
| 53 55 | 
             
                private
         | 
| 54 56 |  | 
| 55 57 | 
             
                def check_uncovered(keywords)
         | 
| 56 | 
            -
                  unknown = keywords -  | 
| 58 | 
            +
                  unknown = keywords - OPTIONALLY_COVERED
         | 
| 57 59 | 
             
                  raise ArgumentError, "unknown options: #{unknown.join(', ')}" unless unknown.empty?
         | 
| 58 60 | 
             
                end
         | 
| 59 61 |  | 
| @@ -65,10 +67,6 @@ module DeepCover | |
| 65 67 | 
             
                  self
         | 
| 66 68 | 
             
                end
         | 
| 67 69 |  | 
| 68 | 
            -
                def copy(h)
         | 
| 69 | 
            -
                  h.dup.transform_values(&:dup).transform_values(&:freeze)
         | 
| 70 | 
            -
                end
         | 
| 71 | 
            -
             | 
| 72 70 | 
             
                module Setter
         | 
| 73 71 | 
             
                  def config(notify = self)
         | 
| 74 72 | 
             
                    @config ||= Config.new(notify)
         | 
    
        data/lib/deep_cover/coverage.rb
    CHANGED
    
    
| @@ -15,6 +15,10 @@ module DeepCover | |
| 15 15 | 
             
                  analyser_map.transform_values { |a| a.transform_values(&:stats) }
         | 
| 16 16 | 
             
                end
         | 
| 17 17 |  | 
| 18 | 
            +
                def self.template
         | 
| 19 | 
            +
                  {node: Analyser::Node, per_char: Analyser::PerChar, branch: Analyser::Branch}
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 18 22 | 
             
                private
         | 
| 19 23 |  | 
| 20 24 | 
             
                def compute_analysers(covered_code)
         | 
| @@ -37,6 +37,7 @@ module DeepCover | |
| 37 37 | 
             
                  case (reporter = options.fetch(:reporter, :html).to_sym)
         | 
| 38 38 | 
             
                  when :html
         | 
| 39 39 | 
             
                    Reporter::HTML.report(self, **options)
         | 
| 40 | 
            +
                    Reporter::Text.report(self, **options) + "\n\nHTML generated: open #{options[:output]}/index.html"
         | 
| 40 41 | 
             
                  when :istanbul
         | 
| 41 42 | 
             
                    if Reporter::Istanbul.available?
         | 
| 42 43 | 
             
                      report_istanbul(**options)
         | 
| @@ -44,28 +45,12 @@ module DeepCover | |
| 44 45 | 
             
                      warn 'nyc not available. Please install `nyc` using `yarn global add nyc` or `npm i nyc -g`'
         | 
| 45 46 | 
             
                    end
         | 
| 46 47 | 
             
                  when :text
         | 
| 47 | 
            -
                     | 
| 48 | 
            +
                    Reporter::Text.report(self, **options)
         | 
| 48 49 | 
             
                  else
         | 
| 49 50 | 
             
                    raise ArgumentError, "Unknown reporter: #{reporter}"
         | 
| 50 51 | 
             
                  end
         | 
| 51 52 | 
             
                end
         | 
| 52 53 |  | 
| 53 | 
            -
                def basic_report
         | 
| 54 | 
            -
                  missing = map do |covered_code|
         | 
| 55 | 
            -
                    if covered_code.has_executed?
         | 
| 56 | 
            -
                      missed = covered_code.line_coverage.each_with_index.map do |line_cov, line_index|
         | 
| 57 | 
            -
                        line_index + 1 if line_cov == 0
         | 
| 58 | 
            -
                      end.compact
         | 
| 59 | 
            -
                    else
         | 
| 60 | 
            -
                      missed = ['all']
         | 
| 61 | 
            -
                    end
         | 
| 62 | 
            -
                    [covered_code.buffer.name, missed] unless missed.empty?
         | 
| 63 | 
            -
                  end.compact.to_h
         | 
| 64 | 
            -
                  missing.map do |path, lines|
         | 
| 65 | 
            -
                    "#{File.basename(path)}: #{lines.join(', ')}"
         | 
| 66 | 
            -
                  end.join("\n")
         | 
| 67 | 
            -
                end
         | 
| 68 | 
            -
             | 
| 69 54 | 
             
                def self.load(dest_path, dirname = 'deep_cover', with_trackers: true)
         | 
| 70 55 | 
             
                  Persistence.new(dest_path, dirname).load(with_trackers: with_trackers)
         | 
| 71 56 | 
             
                end
         | 
| @@ -3,7 +3,6 @@ | |
| 3 3 | 
             
            module DeepCover
         | 
| 4 4 | 
             
              require 'securerandom'
         | 
| 5 5 | 
             
              class Coverage::Persistence
         | 
| 6 | 
            -
                # rubocop:disable Security/MarshalLoad
         | 
| 7 6 | 
             
                BASENAME = 'coverage.dc'
         | 
| 8 7 | 
             
                TRACKER_TEMPLATE = 'trackers%{unique}.dct'
         | 
| 9 8 |  | 
| @@ -56,6 +55,7 @@ module DeepCover | |
| 56 55 | 
             
                  ))
         | 
| 57 56 | 
             
                end
         | 
| 58 57 |  | 
| 58 | 
            +
                # rubocop:disable Security/MarshalLoad
         | 
| 59 59 | 
             
                def load_coverage
         | 
| 60 60 | 
             
                  Marshal.load(dir_path.join(BASENAME).binread).tap do |version: raise, coverage: raise|
         | 
| 61 61 | 
             
                    raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
         | 
| @@ -71,6 +71,7 @@ module DeepCover | |
| 71 71 | 
             
                    end
         | 
| 72 72 | 
             
                  end
         | 
| 73 73 | 
             
                end
         | 
| 74 | 
            +
                # rubocop:enable Security/MarshalLoad
         | 
| 74 75 |  | 
| 75 76 | 
             
                def merge_trackers(hash, to_merge)
         | 
| 76 77 | 
             
                  hash.merge!(to_merge) do |_key, current, to_add|
         | 
| @@ -1,6 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module DeepCover
         | 
| 4 | 
            +
              bootstrap
         | 
| 5 | 
            +
              load_parser
         | 
| 6 | 
            +
             | 
| 4 7 | 
             
              class CoveredCode
         | 
| 5 8 | 
             
                DEFAULT_TRACKER_GLOBAL = '$_cov'
         | 
| 6 9 |  | 
| @@ -11,7 +14,7 @@ module DeepCover | |
| 11 14 | 
             
                def initialize(path: nil, source: nil, lineno: 1, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
         | 
| 12 15 | 
             
                  raise 'Must provide either path or source' unless path || source
         | 
| 13 16 |  | 
| 14 | 
            -
                  @buffer =  | 
| 17 | 
            +
                  @buffer = Parser::Source::Buffer.new(path, lineno)
         | 
| 15 18 | 
             
                  @buffer.source = source || File.read(path)
         | 
| 16 19 | 
             
                  @tracker_count = 0
         | 
| 17 20 | 
             
                  @tracker_global = tracker_global
         | 
| @@ -88,7 +91,7 @@ module DeepCover | |
| 88 91 |  | 
| 89 92 | 
             
                def root
         | 
| 90 93 | 
             
                  @root ||= begin
         | 
| 91 | 
            -
                    ast =  | 
| 94 | 
            +
                    ast = parser.parse(@buffer)
         | 
| 92 95 | 
             
                    Node::Root.new(ast, self)
         | 
| 93 96 | 
             
                  end
         | 
| 94 97 | 
             
                end
         | 
| @@ -98,7 +101,7 @@ module DeepCover | |
| 98 101 | 
             
                end
         | 
| 99 102 |  | 
| 100 103 | 
             
                def instrument_source
         | 
| 101 | 
            -
                  rewriter =  | 
| 104 | 
            +
                  rewriter = Parser::Source::TreeRewriter.new(@buffer)
         | 
| 102 105 | 
             
                  covered_ast.each_node(:postorder) do |node|
         | 
| 103 106 | 
             
                    node.rewriting_rules.each do |range, rule|
         | 
| 104 107 | 
             
                      prefix, _node, suffix = rule.partition('%{node}')
         | 
| @@ -132,5 +135,14 @@ module DeepCover | |
| 132 135 | 
             
                def global
         | 
| 133 136 | 
             
                  @@globals[tracker_global]
         | 
| 134 137 | 
             
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                private
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                def parser
         | 
| 142 | 
            +
                  Parser::CurrentRuby.new.tap do |parser|
         | 
| 143 | 
            +
                    parser.diagnostics.all_errors_are_fatal = true
         | 
| 144 | 
            +
                    parser.diagnostics.ignore_warnings      = true
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
                end
         | 
| 135 147 | 
             
              end
         | 
| 136 148 | 
             
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module DeepCover
         | 
| 4 | 
            +
              module Load
         | 
| 5 | 
            +
                AUTOLOAD = %i[analyser autoload_tracker coverage covered_code custom_requirer memoize
         | 
| 6 | 
            +
                              module_override node problem_with_diagnostic reporter
         | 
| 7 | 
            +
                             ]
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def load_absolute_basics
         | 
| 10 | 
            +
                  require_relative 'base'
         | 
| 11 | 
            +
                  require_relative 'config'
         | 
| 12 | 
            +
                  require_relative 'tools/camelize'
         | 
| 13 | 
            +
                  AUTOLOAD.each do |module_name|
         | 
| 14 | 
            +
                    DeepCover.autoload(Tools::Camelize.camelize(module_name), "#{__dir__}/#{module_name}")
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                  Object.autoload :Term, 'term/ansicolor'
         | 
| 17 | 
            +
                  Object.autoload :Terminal, 'terminal-table'
         | 
| 18 | 
            +
                  require 'pry'
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def bootstrap
         | 
| 22 | 
            +
                  return if @bootstrapped
         | 
| 23 | 
            +
                  require_relative 'backports'
         | 
| 24 | 
            +
                  require_relative 'tools'
         | 
| 25 | 
            +
                  @bootstrapped = true
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def load_parser
         | 
| 29 | 
            +
                  return if @parser_loaded
         | 
| 30 | 
            +
                  require 'parser'
         | 
| 31 | 
            +
                  silence_warnings do
         | 
| 32 | 
            +
                    require 'parser/current'
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                  require_relative_dir 'parser_ext'
         | 
| 35 | 
            +
                  @parser_loaded
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def load_all
         | 
| 39 | 
            +
                  return if @all_loaded
         | 
| 40 | 
            +
                  bootstrap
         | 
| 41 | 
            +
                  load_parser
         | 
| 42 | 
            +
                  AUTOLOAD.each do |module_name|
         | 
| 43 | 
            +
                    DeepCover.const_get(Tools::Camelize.camelize(module_name))
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                  @all_loaded = true
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              extend Load
         | 
| 50 | 
            +
            end
         | 
    
        data/lib/deep_cover/memoize.rb
    CHANGED
    
    
    
        data/lib/deep_cover/node.rb
    CHANGED
    
    | @@ -1,6 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module DeepCover
         | 
| 4 | 
            +
              bootstrap
         | 
| 5 | 
            +
              load_parser
         | 
| 6 | 
            +
             | 
| 4 7 | 
             
              class Node
         | 
| 5 8 | 
             
                # Reopened in base
         | 
| 6 9 | 
             
                CLASSES = []
         | 
| @@ -13,7 +16,6 @@ module DeepCover | |
| 13 16 | 
             
              require_relative 'node/base'
         | 
| 14 17 | 
             
              require_relative_dir 'node'
         | 
| 15 18 |  | 
| 16 | 
            -
              require_relative 'memoize'
         | 
| 17 19 | 
             
              Node.include Memoize
         | 
| 18 20 | 
             
              Node::CLASSES.freeze.each do |klass|
         | 
| 19 21 | 
             
                klass.memoize :flow_entry_count, :flow_completion_count, :execution_count, :loc_hash
         | 
    
        data/lib/deep_cover/node/root.rb
    CHANGED
    
    | @@ -4,7 +4,7 @@ module DeepCover | |
| 4 4 | 
             
              class Node::Root < Node
         | 
| 5 5 | 
             
                has_tracker :root
         | 
| 6 6 | 
             
                has_child main: Node,
         | 
| 7 | 
            -
                          can_be_empty: -> {  | 
| 7 | 
            +
                          can_be_empty: -> { Parser::Source::Range.new(covered_code.buffer, 0, 0) },
         | 
| 8 8 | 
             
                          is_statement: true,
         | 
| 9 9 | 
             
                          rewrite: -> {
         | 
| 10 10 | 
             
                            "#{covered_code.trackers_setup_source};%{root_tracker};%{local}=nil;%{node}"
         | 
| @@ -6,7 +6,7 @@ module DeepCover | |
| 6 6 |  | 
| 7 7 | 
             
                def initialize(covered_code, line_range, original_exception = nil)
         | 
| 8 8 | 
             
                  @covered_code = covered_code
         | 
| 9 | 
            -
                  if line_range. | 
| 9 | 
            +
                  if line_range.respond_to? :last_line
         | 
| 10 10 | 
             
                    @line_range = line_range.line..line_range.last_line
         | 
| 11 11 | 
             
                  else
         | 
| 12 12 | 
             
                    @line_range = line_range
         | 
    
        data/lib/deep_cover/reporter.rb
    CHANGED
    
    
| @@ -1,23 +1,35 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module DeepCover
         | 
| 4 | 
            -
               | 
| 5 | 
            -
                require_relative 'tree'
         | 
| 6 | 
            -
                require_relative 'base'
         | 
| 4 | 
            +
              require_relative 'base'
         | 
| 7 5 |  | 
| 6 | 
            +
              module Reporter
         | 
| 8 7 | 
             
                class HTML::Index < Struct.new(:analysis, :options)
         | 
| 9 8 | 
             
                  def initialize(analysis, **options)
         | 
| 10 9 | 
             
                    raise ArgumentError unless analysis.is_a? Coverage::Analysis
         | 
| 11 10 | 
             
                    super
         | 
| 12 11 | 
             
                  end
         | 
| 13 12 |  | 
| 14 | 
            -
                  include  | 
| 13 | 
            +
                  include Util::Tree
         | 
| 15 14 | 
             
                  include HTML::Base
         | 
| 16 15 |  | 
| 17 16 | 
             
                  def stats_to_data
         | 
| 18 | 
            -
                     | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 17 | 
            +
                    populate_stats(analysis) do |full_path, partial_path, data, children|
         | 
| 18 | 
            +
                      data = transform_data(data)
         | 
| 19 | 
            +
                      if children.empty?
         | 
| 20 | 
            +
                        {
         | 
| 21 | 
            +
                          text: %{<a href="#{full_path}.html">#{partial_path}</a>},
         | 
| 22 | 
            +
                          data: data,
         | 
| 23 | 
            +
                        }
         | 
| 24 | 
            +
                      else
         | 
| 25 | 
            +
                        {
         | 
| 26 | 
            +
                          text: partial_path,
         | 
| 27 | 
            +
                          data: data,
         | 
| 28 | 
            +
                          children: children,
         | 
| 29 | 
            +
                          state: {opened: true},
         | 
| 30 | 
            +
                        }
         | 
| 31 | 
            +
                      end
         | 
| 32 | 
            +
                    end
         | 
| 21 33 | 
             
                  end
         | 
| 22 34 |  | 
| 23 35 | 
             
                  def columns
         | 
| @@ -38,40 +50,12 @@ module DeepCover | |
| 38 50 |  | 
| 39 51 | 
             
                  private
         | 
| 40 52 |  | 
| 41 | 
            -
                  # {a: {}}    => [{text: a, data: stat_map[a]}]
         | 
| 42 | 
            -
                  # {b: {...}} => [{text: b, data: sum(stats), children: [...]}]
         | 
| 43 | 
            -
                  def populate(tree, dir = '')
         | 
| 44 | 
            -
                    tree.map do |path, children_hash|
         | 
| 45 | 
            -
                      full_path = [dir, path].join
         | 
| 46 | 
            -
                      if children_hash.empty?
         | 
| 47 | 
            -
                        {
         | 
| 48 | 
            -
                          text: %{<a href="#{full_path}.html">#{path}</a>},
         | 
| 49 | 
            -
                          data: @map[full_path],
         | 
| 50 | 
            -
                        }
         | 
| 51 | 
            -
                      else
         | 
| 52 | 
            -
                        children = populate(children_hash, "#{full_path}/")
         | 
| 53 | 
            -
                        data = Tools.merge(*children.map { |c| c[:data] }, :+)
         | 
| 54 | 
            -
                        {
         | 
| 55 | 
            -
                          text: path,
         | 
| 56 | 
            -
                          data: data,
         | 
| 57 | 
            -
                          children: children,
         | 
| 58 | 
            -
                          state: {opened: true},
         | 
| 59 | 
            -
                        }
         | 
| 60 | 
            -
                      end
         | 
| 61 | 
            -
                    end
         | 
| 62 | 
            -
                  end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                  # Modifies in place the tree:
         | 
| 65 53 | 
             
                  # {per_char: Stat, ...} => {per_char: {ignored: ...}, per_char_percent: 55.55, ...}
         | 
| 66 | 
            -
                  def transform_data( | 
| 67 | 
            -
                     | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
                          *node[:data].map { |type, stat| {:"#{type}_percent" => stat.percent_covered} }
         | 
| 72 | 
            -
                      )
         | 
| 73 | 
            -
                      transform_data(node[:children])
         | 
| 74 | 
            -
                    end
         | 
| 54 | 
            +
                  def transform_data(data)
         | 
| 55 | 
            +
                    Tools.merge(
         | 
| 56 | 
            +
                        data.transform_values(&:to_h),
         | 
| 57 | 
            +
                        *data.map { |type, stat| {:"#{type}_percent" => stat.percent_covered} }
         | 
| 58 | 
            +
                    )
         | 
| 75 59 | 
             
                  end
         | 
| 76 60 | 
             
                end
         | 
| 77 61 | 
             
              end
         | 
| @@ -25,7 +25,7 @@ module DeepCover | |
| 25 25 | 
             
                  end
         | 
| 26 26 |  | 
| 27 27 | 
             
                  def convert_source
         | 
| 28 | 
            -
                    @rewriter =  | 
| 28 | 
            +
                    @rewriter = Parser::Source::TreeRewriter.new(covered_code.buffer)
         | 
| 29 29 | 
             
                    insert_node_tags
         | 
| 30 30 | 
             
                    insert_branch_tags
         | 
| 31 31 | 
             
                    html_escape
         | 
| @@ -126,7 +126,7 @@ module DeepCover | |
| 126 126 | 
             
                    {'<' => '<', '>' => '>', '&' => '&'}.each do |char, escaped|
         | 
| 127 127 | 
             
                      source.scan(char) do
         | 
| 128 128 | 
             
                        m = Regexp.last_match
         | 
| 129 | 
            -
                        range =  | 
| 129 | 
            +
                        range = Parser::Source::Range.new(buffer, m.begin(0), m.end(0))
         | 
| 130 130 | 
             
                        @rewriter.replace(range, escaped)
         | 
| 131 131 | 
             
                      end
         | 
| 132 132 | 
             
                    end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module DeepCover
         | 
| 4 | 
            +
              module Reporter
         | 
| 5 | 
            +
                class Text
         | 
| 6 | 
            +
                  include Util::Tree
         | 
| 7 | 
            +
                  def initialize(coverage, **options)
         | 
| 8 | 
            +
                    @coverage = coverage
         | 
| 9 | 
            +
                    @options = options
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def analysis
         | 
| 13 | 
            +
                    Coverage::Analysis.new(@coverage.covered_codes, **@options)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  INDENT = '  '
         | 
| 17 | 
            +
                  def report
         | 
| 18 | 
            +
                    formatted_headings = headings.map.with_index { |h, i| {value: h, alignment: :center} }
         | 
| 19 | 
            +
                    columns = rows.transpose
         | 
| 20 | 
            +
                    (1...columns.size).step(2) { |i| columns[i] = formatted_stats(columns[i]) }
         | 
| 21 | 
            +
                    table = Terminal::Table.new(
         | 
| 22 | 
            +
                        headings: formatted_headings,
         | 
| 23 | 
            +
                        rows: columns.transpose,
         | 
| 24 | 
            +
                        style: {border_bottom: false, border_top: false, alignment: :right},
         | 
| 25 | 
            +
                    )
         | 
| 26 | 
            +
                    table.align_column 0, :left
         | 
| 27 | 
            +
                    table.render
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def self.report(coverage, **options)
         | 
| 31 | 
            +
                    Text.new(coverage, **options).report
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def formatted_stats(data)
         | 
| 37 | 
            +
                    columns = data.transpose
         | 
| 38 | 
            +
                    columns[1..1] = [] if columns[1].none?
         | 
| 39 | 
            +
                    Terminal::Table.new(
         | 
| 40 | 
            +
                        rows: columns.transpose,
         | 
| 41 | 
            +
                        style: {border_x: '', border_bottom: false, border_top: false, alignment: :right}
         | 
| 42 | 
            +
                    ).render.gsub(' | ', ' ').gsub(/ ?\| ?/, '').split("\n")
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def rows
         | 
| 46 | 
            +
                    populate_stats(analysis).map do |full_path, partial_path, data, children|
         | 
| 47 | 
            +
                      [partial_path, *transform_data(data)]
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def headings
         | 
| 52 | 
            +
                    Coverage::Analysis.template.values.flat_map do |analyser|
         | 
| 53 | 
            +
                      [analyser.human_name, '%']
         | 
| 54 | 
            +
                    end.unshift('Path')
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  # {per_char: Stat, ...} => ['1 [+2] / 3', '100 %', ...]
         | 
| 58 | 
            +
                  def transform_data(data)
         | 
| 59 | 
            +
                    data.flat_map do |type, stat|
         | 
| 60 | 
            +
                      ignored = "[+#{stat.ignored}]" if stat.ignored > 0
         | 
| 61 | 
            +
                      [[stat.executed, ignored, '/', stat.potentially_executable], format('%.2f', stat.percent_covered)]
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module DeepCover
         | 
| 4 | 
            +
              module Reporter
         | 
| 5 | 
            +
                module Util
         | 
| 6 | 
            +
                  # Utility functions to deal with trees
         | 
| 7 | 
            +
                  module Tree
         | 
| 8 | 
            +
                    def paths_to_tree(paths)
         | 
| 9 | 
            +
                      twigs = paths.map do |path|
         | 
| 10 | 
            +
                        partials = path_to_partial_paths(path)
         | 
| 11 | 
            +
                        list_to_twig(partials)
         | 
| 12 | 
            +
                      end
         | 
| 13 | 
            +
                      tree = deep_merge(twigs)
         | 
| 14 | 
            +
                      simplify(tree)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    # 'some/example/path' => %w[some example path]
         | 
| 18 | 
            +
                    def path_to_partial_paths(path)
         | 
| 19 | 
            +
                      path.to_s.split('/')
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    # A twig is a tree with only single branches
         | 
| 23 | 
            +
                    # [a, b, c] =>
         | 
| 24 | 
            +
                    #   {a: {b: {c: {} } } }
         | 
| 25 | 
            +
                    def list_to_twig(items)
         | 
| 26 | 
            +
                      result = {}
         | 
| 27 | 
            +
                      items.inject(result) do |parent, value|
         | 
| 28 | 
            +
                        parent[value] = {}
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
                      result
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    #   [{a: {b: {c: {} } } }
         | 
| 34 | 
            +
                    #    {a: {b: {d: {} } } }]
         | 
| 35 | 
            +
                    # => {a: {b: {c: {}, d: {} }}}
         | 
| 36 | 
            +
                    def deep_merge(trees)
         | 
| 37 | 
            +
                      trees.inject do |result, h|
         | 
| 38 | 
            +
                        result.merge(h) { |k, val, val_b| deep_merge([val, val_b]) }
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    # {a: {b: {c: {}, d: {} }}}
         | 
| 43 | 
            +
                    # => {a/b: {c: {}, d: {} }}
         | 
| 44 | 
            +
                    def simplify(tree)
         | 
| 45 | 
            +
                      tree.map do |key, sub_tree|
         | 
| 46 | 
            +
                        sub_tree = simplify(sub_tree)
         | 
| 47 | 
            +
                        if sub_tree.size == 1
         | 
| 48 | 
            +
                          key2, sub_tree = sub_tree.first
         | 
| 49 | 
            +
                          key = "#{key}/#{key2}"
         | 
| 50 | 
            +
                        end
         | 
| 51 | 
            +
                        [key, sub_tree]
         | 
| 52 | 
            +
                      end.to_h
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    # {a: {b: {}}}    => [ra, rb]
         | 
| 56 | 
            +
                    # where rb = yield('a/b', 'b', [])
         | 
| 57 | 
            +
                    # and ra = yield('a', 'a', [rb])
         | 
| 58 | 
            +
                    def populate(tree, dir = '', &block)
         | 
| 59 | 
            +
                      return to_enum(__method__, tree, dir) unless block_given?
         | 
| 60 | 
            +
                      tree.map do |path, children_hash|
         | 
| 61 | 
            +
                        full_path = [dir, path].join
         | 
| 62 | 
            +
                        children = populate(children_hash, "#{full_path}/", &block)
         | 
| 63 | 
            +
                        yield full_path, path, children
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    # Same as populate, but also yields data, which is either the analysis data (for leaves)
         | 
| 68 | 
            +
                    # of the sum of the children (for subtrees)
         | 
| 69 | 
            +
                    def populate_stats(analysis)
         | 
| 70 | 
            +
                      return to_enum(__method__, analysis) unless block_given?
         | 
| 71 | 
            +
                      map = Tools.transform_keys(analysis.stat_map, &:name)
         | 
| 72 | 
            +
                      tree = paths_to_tree(map.keys)
         | 
| 73 | 
            +
                      final_results, _final_data = populate(tree) do |full_path, partial_path, children|
         | 
| 74 | 
            +
                        if children.empty?
         | 
| 75 | 
            +
                          data = map[full_path]
         | 
| 76 | 
            +
                        else
         | 
| 77 | 
            +
                          child_results, child_data = children.transpose
         | 
| 78 | 
            +
                          data = Tools.merge(*child_data, :+)
         | 
| 79 | 
            +
                        end
         | 
| 80 | 
            +
                        result = yield full_path, partial_path, data, child_results || []
         | 
| 81 | 
            +
                        [result, data]
         | 
| 82 | 
            +
                      end.transpose
         | 
| 83 | 
            +
                      final_results
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
            end
         | 
    
        data/lib/deep_cover/tools.rb
    CHANGED
    
    
| @@ -1,10 +1,13 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module DeepCover
         | 
| 4 | 
            -
              module Tools | 
| 5 | 
            -
                 | 
| 6 | 
            -
             | 
| 7 | 
            -
                   | 
| 4 | 
            +
              module Tools
         | 
| 5 | 
            +
                module Camelize
         | 
| 6 | 
            +
                  extend self # Loaded before bootstrap
         | 
| 7 | 
            +
                  # Poor man's camelize. 'an_example' => 'AnExample'
         | 
| 8 | 
            +
                  def camelize(string)
         | 
| 9 | 
            +
                    string.to_s.gsub(/([a-z\d]*)[_?!]?/) { Regexp.last_match(1).capitalize }
         | 
| 10 | 
            +
                  end
         | 
| 8 11 | 
             
                end
         | 
| 9 12 | 
             
              end
         | 
| 10 13 | 
             
            end
         | 
    
        data/lib/deep_cover/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: deep-cover
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Marc-André Lafortune
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: exe
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2017-12- | 
| 12 | 
            +
            date: 2017-12-18 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: parser
         | 
| @@ -109,6 +109,20 @@ dependencies: | |
| 109 109 | 
             
                - - ">="
         | 
| 110 110 | 
             
                  - !ruby/object:Gem::Version
         | 
| 111 111 | 
             
                    version: '0'
         | 
| 112 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 113 | 
            +
              name: terminal-table
         | 
| 114 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 115 | 
            +
                requirements:
         | 
| 116 | 
            +
                - - ">="
         | 
| 117 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 118 | 
            +
                    version: '0'
         | 
| 119 | 
            +
              type: :runtime
         | 
| 120 | 
            +
              prerelease: false
         | 
| 121 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 122 | 
            +
                requirements:
         | 
| 123 | 
            +
                - - ">="
         | 
| 124 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 125 | 
            +
                    version: '0'
         | 
| 112 126 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 113 127 | 
             
              name: pry
         | 
| 114 128 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -271,6 +285,7 @@ files: | |
| 271 285 | 
             
            - lib/deep_cover/coverage/persistence.rb
         | 
| 272 286 | 
             
            - lib/deep_cover/covered_code.rb
         | 
| 273 287 | 
             
            - lib/deep_cover/custom_requirer.rb
         | 
| 288 | 
            +
            - lib/deep_cover/load.rb
         | 
| 274 289 | 
             
            - lib/deep_cover/memoize.rb
         | 
| 275 290 | 
             
            - lib/deep_cover/module_override.rb
         | 
| 276 291 | 
             
            - lib/deep_cover/node.rb
         | 
| @@ -327,8 +342,9 @@ files: | |
| 327 342 | 
             
            - lib/deep_cover/reporter/html/template/assets/throbber.gif
         | 
| 328 343 | 
             
            - lib/deep_cover/reporter/html/template/index.html.erb
         | 
| 329 344 | 
             
            - lib/deep_cover/reporter/html/template/source.html.erb
         | 
| 330 | 
            -
            - lib/deep_cover/reporter/html/tree.rb
         | 
| 331 345 | 
             
            - lib/deep_cover/reporter/istanbul.rb
         | 
| 346 | 
            +
            - lib/deep_cover/reporter/text.rb
         | 
| 347 | 
            +
            - lib/deep_cover/reporter/util/tree.rb
         | 
| 332 348 | 
             
            - lib/deep_cover/tools.rb
         | 
| 333 349 | 
             
            - lib/deep_cover/tools/builtin_coverage.rb
         | 
| 334 350 | 
             
            - lib/deep_cover/tools/camelize.rb
         | 
| @@ -1,55 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module DeepCover
         | 
| 4 | 
            -
              module Reporter
         | 
| 5 | 
            -
                # Utility functions to deal with trees
         | 
| 6 | 
            -
                module HTML::Tree
         | 
| 7 | 
            -
                  def paths_to_tree(paths)
         | 
| 8 | 
            -
                    twigs = paths.map do |path|
         | 
| 9 | 
            -
                      partials = path_to_partial_paths(path)
         | 
| 10 | 
            -
                      list_to_twig(partials)
         | 
| 11 | 
            -
                    end
         | 
| 12 | 
            -
                    tree = deep_merge(twigs)
         | 
| 13 | 
            -
                    simplify(tree)
         | 
| 14 | 
            -
                  end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                  # 'some/example/path' => %w[some example path]
         | 
| 17 | 
            -
                  def path_to_partial_paths(path)
         | 
| 18 | 
            -
                    path.to_s.split('/')
         | 
| 19 | 
            -
                  end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  # A twig is a tree with only single branches
         | 
| 22 | 
            -
                  # [a, b, c] =>
         | 
| 23 | 
            -
                  #   {a: {b: {c: {} } } }
         | 
| 24 | 
            -
                  def list_to_twig(items)
         | 
| 25 | 
            -
                    result = {}
         | 
| 26 | 
            -
                    items.inject(result) do |parent, value|
         | 
| 27 | 
            -
                      parent[value] = {}
         | 
| 28 | 
            -
                    end
         | 
| 29 | 
            -
                    result
         | 
| 30 | 
            -
                  end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                  #   [{a: {b: {c: {} } } }
         | 
| 33 | 
            -
                  #    {a: {b: {d: {} } } }]
         | 
| 34 | 
            -
                  # => {a: {b: {c: {}, d: {} }}}
         | 
| 35 | 
            -
                  def deep_merge(trees)
         | 
| 36 | 
            -
                    trees.inject do |result, h|
         | 
| 37 | 
            -
                      result.merge(h) { |k, val, val_b| deep_merge([val, val_b]) }
         | 
| 38 | 
            -
                    end
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                  # {a: {b: {c: {}, d: {} }}}
         | 
| 42 | 
            -
                  # => {a/b: {c: {}, d: {} }}
         | 
| 43 | 
            -
                  def simplify(tree)
         | 
| 44 | 
            -
                    tree.map do |key, sub_tree|
         | 
| 45 | 
            -
                      sub_tree = simplify(sub_tree)
         | 
| 46 | 
            -
                      if sub_tree.size == 1
         | 
| 47 | 
            -
                        key2, sub_tree = sub_tree.first
         | 
| 48 | 
            -
                        key = "#{key}/#{key2}"
         | 
| 49 | 
            -
                      end
         | 
| 50 | 
            -
                      [key, sub_tree]
         | 
| 51 | 
            -
                    end.to_h
         | 
| 52 | 
            -
                  end
         | 
| 53 | 
            -
                end
         | 
| 54 | 
            -
              end
         | 
| 55 | 
            -
            end
         |