spoom 1.0.2 → 1.0.7
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/Gemfile +4 -0
 - data/README.md +253 -1
 - data/Rakefile +2 -0
 - data/exe/spoom +7 -0
 - data/lib/spoom.rb +9 -1
 - data/lib/spoom/cli.rb +68 -0
 - data/lib/spoom/cli/bump.rb +60 -0
 - data/lib/spoom/cli/config.rb +51 -0
 - data/lib/spoom/cli/coverage.rb +191 -0
 - data/lib/spoom/cli/helper.rb +74 -0
 - data/lib/spoom/cli/lsp.rb +165 -0
 - data/lib/spoom/cli/run.rb +79 -0
 - data/lib/spoom/config.rb +13 -0
 - data/lib/spoom/coverage.rb +73 -0
 - data/lib/spoom/coverage/d3.rb +110 -0
 - data/lib/spoom/coverage/d3/base.rb +50 -0
 - data/lib/spoom/coverage/d3/circle_map.rb +195 -0
 - data/lib/spoom/coverage/d3/pie.rb +175 -0
 - data/lib/spoom/coverage/d3/timeline.rb +486 -0
 - data/lib/spoom/coverage/report.rb +308 -0
 - data/lib/spoom/coverage/snapshot.rb +132 -0
 - data/lib/spoom/file_tree.rb +196 -0
 - data/lib/spoom/git.rb +98 -0
 - data/lib/spoom/printer.rb +81 -0
 - data/lib/spoom/sorbet.rb +83 -0
 - data/lib/spoom/sorbet/config.rb +21 -9
 - data/lib/spoom/sorbet/errors.rb +139 -0
 - data/lib/spoom/sorbet/lsp.rb +196 -0
 - data/lib/spoom/sorbet/lsp/base.rb +58 -0
 - data/lib/spoom/sorbet/lsp/errors.rb +45 -0
 - data/lib/spoom/sorbet/lsp/structures.rb +312 -0
 - data/lib/spoom/sorbet/metrics.rb +33 -0
 - data/lib/spoom/sorbet/sigils.rb +98 -0
 - data/lib/spoom/test_helpers/project.rb +103 -0
 - data/lib/spoom/timeline.rb +53 -0
 - data/lib/spoom/version.rb +2 -1
 - data/templates/card.erb +8 -0
 - data/templates/card_snapshot.erb +22 -0
 - data/templates/page.erb +50 -0
 - metadata +80 -20
 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Spoom
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Cli
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Run < Thor
         
     | 
| 
      
 7 
     | 
    
         
            +
                  include Helper
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  default_task :tc
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  desc "tc", "run srb tc"
         
     | 
| 
      
 12 
     | 
    
         
            +
                  option :limit, type: :numeric, aliases: :l
         
     | 
| 
      
 13 
     | 
    
         
            +
                  option :code, type: :numeric, aliases: :c
         
     | 
| 
      
 14 
     | 
    
         
            +
                  option :sort, type: :string, aliases: :s
         
     | 
| 
      
 15 
     | 
    
         
            +
                  def tc
         
     | 
| 
      
 16 
     | 
    
         
            +
                    in_sorbet_project!
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    path = exec_path
         
     | 
| 
      
 19 
     | 
    
         
            +
                    limit = options[:limit]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    sort = options[:sort]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    code = options[:code]
         
     | 
| 
      
 22 
     | 
    
         
            +
                    colors = options[:color]
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    unless limit || code || sort
         
     | 
| 
      
 25 
     | 
    
         
            +
                      return Spoom::Sorbet.srb_tc(path: path, capture_err: false).last
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    output, status = Spoom::Sorbet.srb_tc(path: path, capture_err: true)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    if status
         
     | 
| 
      
 30 
     | 
    
         
            +
                      $stderr.print(output)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      return 0
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    errors = Spoom::Sorbet::Errors::Parser.parse_string(output)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    errors_count = errors.size
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    errors = sort == "code" ? errors.sort_by { |e| [e.code, e.file, e.line, e.message] } : errors.sort
         
     | 
| 
      
 38 
     | 
    
         
            +
                    errors = errors.select { |e| e.code == code } if code
         
     | 
| 
      
 39 
     | 
    
         
            +
                    errors = T.must(errors.slice(0, limit)) if limit
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    errors.each do |e|
         
     | 
| 
      
 42 
     | 
    
         
            +
                      code = colorize_code(e.code, colors)
         
     | 
| 
      
 43 
     | 
    
         
            +
                      message = colorize_message(e.message, colors)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      $stderr.puts "#{code} - #{e.file}:#{e.line}: #{message}"
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    if errors_count == errors.size
         
     | 
| 
      
 48 
     | 
    
         
            +
                      $stderr.puts "Errors: #{errors_count}"
         
     | 
| 
      
 49 
     | 
    
         
            +
                    else
         
     | 
| 
      
 50 
     | 
    
         
            +
                      $stderr.puts "Errors: #{errors.size} shown, #{errors_count} total"
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    1
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  no_commands do
         
     | 
| 
      
 57 
     | 
    
         
            +
                    def colorize_code(code, colors = true)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      return code.to_s unless colors
         
     | 
| 
      
 59 
     | 
    
         
            +
                      code.to_s.light_black
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    def colorize_message(message, colors = true)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      return message unless colors
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                      cyan = T.let(false, T::Boolean)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      word = StringIO.new
         
     | 
| 
      
 67 
     | 
    
         
            +
                      message.chars.each do |c|
         
     | 
| 
      
 68 
     | 
    
         
            +
                        if c == '`'
         
     | 
| 
      
 69 
     | 
    
         
            +
                          cyan = !cyan
         
     | 
| 
      
 70 
     | 
    
         
            +
                          next
         
     | 
| 
      
 71 
     | 
    
         
            +
                        end
         
     | 
| 
      
 72 
     | 
    
         
            +
                        word << (cyan ? c.cyan : c.red)
         
     | 
| 
      
 73 
     | 
    
         
            +
                      end
         
     | 
| 
      
 74 
     | 
    
         
            +
                      word.string
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/spoom/config.rb
    ADDED
    
    | 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require 'pathname'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Spoom
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Config
         
     | 
| 
      
 8 
     | 
    
         
            +
                SORBET_CONFIG = "sorbet/config"
         
     | 
| 
      
 9 
     | 
    
         
            +
                SORBET_GEM_PATH = Gem::Specification.find_by_name("sorbet-static").full_gem_path
         
     | 
| 
      
 10 
     | 
    
         
            +
                SORBET_PATH = (Pathname.new(SORBET_GEM_PATH) / "libexec" / "sorbet").to_s
         
     | 
| 
      
 11 
     | 
    
         
            +
                SPOOM_PATH = (Pathname.new(__FILE__) / ".." / ".." / "..").to_s
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,73 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "coverage/snapshot"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative "coverage/report"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative "file_tree"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            require "date"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            module Spoom
         
     | 
| 
      
 11 
     | 
    
         
            +
              module Coverage
         
     | 
| 
      
 12 
     | 
    
         
            +
                extend T::Sig
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                sig { params(path: String).returns(Snapshot) }
         
     | 
| 
      
 15 
     | 
    
         
            +
                def self.snapshot(path: '.')
         
     | 
| 
      
 16 
     | 
    
         
            +
                  snapshot = Snapshot.new
         
     | 
| 
      
 17 
     | 
    
         
            +
                  metrics = Spoom::Sorbet.srb_metrics(path: path, capture_err: true)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  return snapshot unless metrics
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  sha = Spoom::Git.last_commit(path: path)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  snapshot.commit_sha = sha
         
     | 
| 
      
 22 
     | 
    
         
            +
                  snapshot.commit_timestamp = Spoom::Git.commit_timestamp(sha, path: path).to_i if sha
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  snapshot.files = metrics.fetch("types.input.files", 0)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  snapshot.modules = metrics.fetch("types.input.modules.total", 0)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  snapshot.classes = metrics.fetch("types.input.classes.total", 0)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  snapshot.singleton_classes = metrics.fetch("types.input.singleton_classes.total", 0)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  snapshot.methods_with_sig = metrics.fetch("types.sig.count", 0)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  snapshot.methods_without_sig = metrics.fetch("types.input.methods.total", 0) - snapshot.methods_with_sig
         
     | 
| 
      
 30 
     | 
    
         
            +
                  snapshot.calls_typed = metrics.fetch("types.input.sends.typed", 0)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  snapshot.calls_untyped = metrics.fetch("types.input.sends.total", 0) - snapshot.calls_typed
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  snapshot.duration += metrics.fetch("run.utilization.system_time.us", 0)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  snapshot.duration += metrics.fetch("run.utilization.user_time.us", 0)
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  Snapshot::STRICTNESSES.each do |strictness|
         
     | 
| 
      
 37 
     | 
    
         
            +
                    next unless metrics.key?("types.input.files.sigil.#{strictness}")
         
     | 
| 
      
 38 
     | 
    
         
            +
                    snapshot.sigils[strictness] = T.must(metrics["types.input.files.sigil.#{strictness}"])
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  snapshot
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) }
         
     | 
| 
      
 48 
     | 
    
         
            +
                def self.report(snapshots, palette:, path: ".")
         
     | 
| 
      
 49 
     | 
    
         
            +
                  intro_commit = Git.sorbet_intro_commit(path: path)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  intro_date = intro_commit ? Git.commit_time(intro_commit, path: path) : nil
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  Report.new(
         
     | 
| 
      
 53 
     | 
    
         
            +
                    project_name: File.basename(File.expand_path(path)),
         
     | 
| 
      
 54 
     | 
    
         
            +
                    palette: palette,
         
     | 
| 
      
 55 
     | 
    
         
            +
                    snapshots: snapshots,
         
     | 
| 
      
 56 
     | 
    
         
            +
                    sigils_tree: sigils_tree(path: path),
         
     | 
| 
      
 57 
     | 
    
         
            +
                    sorbet_intro_commit: intro_commit,
         
     | 
| 
      
 58 
     | 
    
         
            +
                    sorbet_intro_date: intro_date,
         
     | 
| 
      
 59 
     | 
    
         
            +
                  )
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                sig { params(path: String).returns(FileTree) }
         
     | 
| 
      
 63 
     | 
    
         
            +
                def self.sigils_tree(path: ".")
         
     | 
| 
      
 64 
     | 
    
         
            +
                  config_file = "#{path}/#{Spoom::Config::SORBET_CONFIG}"
         
     | 
| 
      
 65 
     | 
    
         
            +
                  return FileTree.new unless File.exist?(config_file)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  config = Sorbet::Config.parse_file(config_file)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  files = Sorbet.srb_files(config, path: path)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  files.select! { |file| file =~ /\.rb$/ }
         
     | 
| 
      
 69 
     | 
    
         
            +
                  files.reject! { |file| file =~ %r{/test/} }
         
     | 
| 
      
 70 
     | 
    
         
            +
                  FileTree.new(files, strip_prefix: path)
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,110 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "d3/circle_map"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative "d3/pie"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative "d3/timeline"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Spoom
         
     | 
| 
      
 9 
     | 
    
         
            +
              module Coverage
         
     | 
| 
      
 10 
     | 
    
         
            +
                module D3
         
     | 
| 
      
 11 
     | 
    
         
            +
                  extend T::Sig
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  COLOR_IGNORE = "#999"
         
     | 
| 
      
 14 
     | 
    
         
            +
                  COLOR_FALSE = "#db4437"
         
     | 
| 
      
 15 
     | 
    
         
            +
                  COLOR_TRUE = "#0f9d58"
         
     | 
| 
      
 16 
     | 
    
         
            +
                  COLOR_STRICT = "#0a7340"
         
     | 
| 
      
 17 
     | 
    
         
            +
                  COLOR_STRONG = "#064828"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  sig { returns(String) }
         
     | 
| 
      
 20 
     | 
    
         
            +
                  def self.header_style
         
     | 
| 
      
 21 
     | 
    
         
            +
                    <<~CSS
         
     | 
| 
      
 22 
     | 
    
         
            +
                      svg {
         
     | 
| 
      
 23 
     | 
    
         
            +
                        width: 100%;
         
     | 
| 
      
 24 
     | 
    
         
            +
                        height: 100%;
         
     | 
| 
      
 25 
     | 
    
         
            +
                      }
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      .tooltip {
         
     | 
| 
      
 28 
     | 
    
         
            +
                        font: 12px Arial, sans-serif;
         
     | 
| 
      
 29 
     | 
    
         
            +
                        color: #fff;
         
     | 
| 
      
 30 
     | 
    
         
            +
                        text-align: center;
         
     | 
| 
      
 31 
     | 
    
         
            +
                        background: rgba(0, 0, 0, 0.6);
         
     | 
| 
      
 32 
     | 
    
         
            +
                        padding: 5px;
         
     | 
| 
      
 33 
     | 
    
         
            +
                        border: 0px;
         
     | 
| 
      
 34 
     | 
    
         
            +
                        border-radius: 4px;
         
     | 
| 
      
 35 
     | 
    
         
            +
                        position: absolute;
         
     | 
| 
      
 36 
     | 
    
         
            +
                        top: 0;
         
     | 
| 
      
 37 
     | 
    
         
            +
                        left: 0;
         
     | 
| 
      
 38 
     | 
    
         
            +
                        opacity: 0;
         
     | 
| 
      
 39 
     | 
    
         
            +
                      }
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                      .label {
         
     | 
| 
      
 42 
     | 
    
         
            +
                        font: 14px Arial, sans-serif;
         
     | 
| 
      
 43 
     | 
    
         
            +
                        font-weight: bold;
         
     | 
| 
      
 44 
     | 
    
         
            +
                        fill: #fff;
         
     | 
| 
      
 45 
     | 
    
         
            +
                        text-anchor: middle;
         
     | 
| 
      
 46 
     | 
    
         
            +
                        pointer-events: none;
         
     | 
| 
      
 47 
     | 
    
         
            +
                      }
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                      .label .small {
         
     | 
| 
      
 50 
     | 
    
         
            +
                        font-size: 10px;
         
     | 
| 
      
 51 
     | 
    
         
            +
                      }
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                      #{Pie.header_style}
         
     | 
| 
      
 54 
     | 
    
         
            +
                      #{CircleMap.header_style}
         
     | 
| 
      
 55 
     | 
    
         
            +
                      #{Timeline.header_style}
         
     | 
| 
      
 56 
     | 
    
         
            +
                    CSS
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  sig { params(palette: ColorPalette).returns(String) }
         
     | 
| 
      
 60 
     | 
    
         
            +
                  def self.header_script(palette)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    <<~JS
         
     | 
| 
      
 62 
     | 
    
         
            +
                      var parseDate = d3.timeParse("%s");
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                      function strictnessColor(strictness) {
         
     | 
| 
      
 65 
     | 
    
         
            +
                        switch(strictness) {
         
     | 
| 
      
 66 
     | 
    
         
            +
                          case "ignore":
         
     | 
| 
      
 67 
     | 
    
         
            +
                            return "#{palette.ignore}";
         
     | 
| 
      
 68 
     | 
    
         
            +
                          case "false":
         
     | 
| 
      
 69 
     | 
    
         
            +
                            return "#{palette.false}";
         
     | 
| 
      
 70 
     | 
    
         
            +
                          case "true":
         
     | 
| 
      
 71 
     | 
    
         
            +
                            return "#{palette.true}";
         
     | 
| 
      
 72 
     | 
    
         
            +
                          case "strict":
         
     | 
| 
      
 73 
     | 
    
         
            +
                            return "#{palette.strict}";
         
     | 
| 
      
 74 
     | 
    
         
            +
                          case "strong":
         
     | 
| 
      
 75 
     | 
    
         
            +
                            return "#{palette.strong}";
         
     | 
| 
      
 76 
     | 
    
         
            +
                        }
         
     | 
| 
      
 77 
     | 
    
         
            +
                        return "#{palette.false}";
         
     | 
| 
      
 78 
     | 
    
         
            +
                      }
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                      function toPercent(value, sum) {
         
     | 
| 
      
 81 
     | 
    
         
            +
                        return value ? Math.round(value * 100 / sum) : 0;
         
     | 
| 
      
 82 
     | 
    
         
            +
                      }
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                      var tooltip = d3.select("body")
         
     | 
| 
      
 85 
     | 
    
         
            +
                        .append("div")
         
     | 
| 
      
 86 
     | 
    
         
            +
                          .append("div")
         
     | 
| 
      
 87 
     | 
    
         
            +
                            .attr("class", "tooltip");
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                      function moveTooltip(d) {
         
     | 
| 
      
 90 
     | 
    
         
            +
                        return tooltip
         
     | 
| 
      
 91 
     | 
    
         
            +
                          .style("left", (d3.event.pageX + 20) + "px")
         
     | 
| 
      
 92 
     | 
    
         
            +
                          .style("top", (d3.event.pageY) + "px")
         
     | 
| 
      
 93 
     | 
    
         
            +
                      }
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                      #{Pie.header_script}
         
     | 
| 
      
 96 
     | 
    
         
            +
                      #{CircleMap.header_script}
         
     | 
| 
      
 97 
     | 
    
         
            +
                      #{Timeline.header_script}
         
     | 
| 
      
 98 
     | 
    
         
            +
                    JS
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  class ColorPalette < T::Struct
         
     | 
| 
      
 102 
     | 
    
         
            +
                    prop :ignore, String
         
     | 
| 
      
 103 
     | 
    
         
            +
                    prop :false, String
         
     | 
| 
      
 104 
     | 
    
         
            +
                    prop :true, String
         
     | 
| 
      
 105 
     | 
    
         
            +
                    prop :strict, String
         
     | 
| 
      
 106 
     | 
    
         
            +
                    prop :strong, String
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
                end
         
     | 
| 
      
 109 
     | 
    
         
            +
              end
         
     | 
| 
      
 110 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Spoom
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Coverage
         
     | 
| 
      
 6 
     | 
    
         
            +
                module D3
         
     | 
| 
      
 7 
     | 
    
         
            +
                  class Base
         
     | 
| 
      
 8 
     | 
    
         
            +
                    extend T::Sig
         
     | 
| 
      
 9 
     | 
    
         
            +
                    extend T::Helpers
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    abstract!
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    sig { returns(String) }
         
     | 
| 
      
 14 
     | 
    
         
            +
                    attr_reader :id
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    sig { params(id: String, data: T.untyped).void }
         
     | 
| 
      
 17 
     | 
    
         
            +
                    def initialize(id, data)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      @id = id
         
     | 
| 
      
 19 
     | 
    
         
            +
                      @data = data
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    sig { returns(String) }
         
     | 
| 
      
 23 
     | 
    
         
            +
                    def self.header_style
         
     | 
| 
      
 24 
     | 
    
         
            +
                      ""
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    sig { returns(String) }
         
     | 
| 
      
 28 
     | 
    
         
            +
                    def self.header_script
         
     | 
| 
      
 29 
     | 
    
         
            +
                      ""
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    sig { returns(String) }
         
     | 
| 
      
 33 
     | 
    
         
            +
                    def html
         
     | 
| 
      
 34 
     | 
    
         
            +
                      <<~HTML
         
     | 
| 
      
 35 
     | 
    
         
            +
                        <svg id="#{id}"></svg>
         
     | 
| 
      
 36 
     | 
    
         
            +
                        <script>#{script}</script>
         
     | 
| 
      
 37 
     | 
    
         
            +
                      HTML
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                    sig { returns(String) }
         
     | 
| 
      
 41 
     | 
    
         
            +
                    def tooltip
         
     | 
| 
      
 42 
     | 
    
         
            +
                      ""
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    sig { abstract.returns(String) }
         
     | 
| 
      
 46 
     | 
    
         
            +
                    def script; end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,195 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "base"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Spoom
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Coverage
         
     | 
| 
      
 8 
     | 
    
         
            +
                module D3
         
     | 
| 
      
 9 
     | 
    
         
            +
                  class CircleMap < Base
         
     | 
| 
      
 10 
     | 
    
         
            +
                    extend T::Sig
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    sig { returns(String) }
         
     | 
| 
      
 13 
     | 
    
         
            +
                    def self.header_style
         
     | 
| 
      
 14 
     | 
    
         
            +
                      <<~CSS
         
     | 
| 
      
 15 
     | 
    
         
            +
                        .node {
         
     | 
| 
      
 16 
     | 
    
         
            +
                          cursor: pointer;
         
     | 
| 
      
 17 
     | 
    
         
            +
                        }
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                        .node:hover {
         
     | 
| 
      
 20 
     | 
    
         
            +
                          stroke: #333;
         
     | 
| 
      
 21 
     | 
    
         
            +
                          stroke-width: 1px;
         
     | 
| 
      
 22 
     | 
    
         
            +
                        }
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                        .label.dir {
         
     | 
| 
      
 25 
     | 
    
         
            +
                          fill: #333;
         
     | 
| 
      
 26 
     | 
    
         
            +
                        }
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                        .label.file {
         
     | 
| 
      
 29 
     | 
    
         
            +
                          font: 12px Arial, sans-serif;
         
     | 
| 
      
 30 
     | 
    
         
            +
                        }
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                        .node.root, .node.file {
         
     | 
| 
      
 33 
     | 
    
         
            +
                          pointer-events: none;
         
     | 
| 
      
 34 
     | 
    
         
            +
                        }
         
     | 
| 
      
 35 
     | 
    
         
            +
                      CSS
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    sig { returns(String) }
         
     | 
| 
      
 39 
     | 
    
         
            +
                    def self.header_script
         
     | 
| 
      
 40 
     | 
    
         
            +
                      <<~JS
         
     | 
| 
      
 41 
     | 
    
         
            +
                        function treeHeight(root, height = 0) {
         
     | 
| 
      
 42 
     | 
    
         
            +
                          height += 1;
         
     | 
| 
      
 43 
     | 
    
         
            +
                          if (root.children && root.children.length > 0)
         
     | 
| 
      
 44 
     | 
    
         
            +
                            return Math.max(...root.children.map(child => treeHeight(child, height)));
         
     | 
| 
      
 45 
     | 
    
         
            +
                          else
         
     | 
| 
      
 46 
     | 
    
         
            +
                            return height;
         
     | 
| 
      
 47 
     | 
    
         
            +
                        }
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                        function tooltipMap(d) {
         
     | 
| 
      
 50 
     | 
    
         
            +
                          moveTooltip(d)
         
     | 
| 
      
 51 
     | 
    
         
            +
                            .html("<b>" + d.data.name + "</b>")
         
     | 
| 
      
 52 
     | 
    
         
            +
                        }
         
     | 
| 
      
 53 
     | 
    
         
            +
                      JS
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    sig { override.returns(String) }
         
     | 
| 
      
 57 
     | 
    
         
            +
                    def script
         
     | 
| 
      
 58 
     | 
    
         
            +
                      <<~JS
         
     | 
| 
      
 59 
     | 
    
         
            +
                        var root = {children: #{@data.to_json}}
         
     | 
| 
      
 60 
     | 
    
         
            +
                        var dataHeight = treeHeight(root)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                        var opacity = d3.scaleLinear()
         
     | 
| 
      
 63 
     | 
    
         
            +
                            .domain([0, dataHeight])
         
     | 
| 
      
 64 
     | 
    
         
            +
                            .range([0, 0.2])
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                        root = d3.hierarchy(root)
         
     | 
| 
      
 67 
     | 
    
         
            +
                            .sum((d) => d.children ? d.children.length : 1)
         
     | 
| 
      
 68 
     | 
    
         
            +
                            .sort((a, b) => b.value - a.value);
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                        var dirColor = d3.scaleLinear()
         
     | 
| 
      
 71 
     | 
    
         
            +
                          .domain([1, 0])
         
     | 
| 
      
 72 
     | 
    
         
            +
                          .range([strictnessColor("true"), strictnessColor("false")])
         
     | 
| 
      
 73 
     | 
    
         
            +
                          .interpolate(d3.interpolateRgb);
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                        function redraw() {
         
     | 
| 
      
 76 
     | 
    
         
            +
                          var diameter = document.getElementById("#{id}").clientWidth - 20;
         
     | 
| 
      
 77 
     | 
    
         
            +
                          d3.select("##{id}").selectAll("*").remove()
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                          var svg_#{id} = d3.select("##{id}")
         
     | 
| 
      
 80 
     | 
    
         
            +
                            .attr("width", diameter)
         
     | 
| 
      
 81 
     | 
    
         
            +
                            .attr("height", diameter)
         
     | 
| 
      
 82 
     | 
    
         
            +
                            .append("g")
         
     | 
| 
      
 83 
     | 
    
         
            +
                              .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                          var pack = d3.pack()
         
     | 
| 
      
 86 
     | 
    
         
            +
                              .size([diameter, diameter])
         
     | 
| 
      
 87 
     | 
    
         
            +
                              .padding(2);
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                          var focus = root,
         
     | 
| 
      
 90 
     | 
    
         
            +
                              nodes = pack(root).descendants(),
         
     | 
| 
      
 91 
     | 
    
         
            +
                              view;
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                          var circle = svg_#{id}.selectAll("circle")
         
     | 
| 
      
 94 
     | 
    
         
            +
                            .data(nodes)
         
     | 
| 
      
 95 
     | 
    
         
            +
                            .enter().append("circle")
         
     | 
| 
      
 96 
     | 
    
         
            +
                              .attr("class", (d) => d.parent ? d.children ? "node" : "node file" : "node root")
         
     | 
| 
      
 97 
     | 
    
         
            +
                              .attr("fill", (d) => d.children ? dirColor(d.data.score) : strictnessColor(d.data.strictness))
         
     | 
| 
      
 98 
     | 
    
         
            +
                              .attr("fill-opacity", (d) => d.children ? opacity(d.depth) : 1)
         
     | 
| 
      
 99 
     | 
    
         
            +
                              .on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); })
         
     | 
| 
      
 100 
     | 
    
         
            +
                              .on("mouseover", (d) => tooltip.style("opacity", 1))
         
     | 
| 
      
 101 
     | 
    
         
            +
                              .on("mousemove", tooltipMap)
         
     | 
| 
      
 102 
     | 
    
         
            +
                              .on("mouseleave", (d) => tooltip.style("opacity", 0));
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                          var text = svg_#{id}.selectAll("text")
         
     | 
| 
      
 105 
     | 
    
         
            +
                            .data(nodes)
         
     | 
| 
      
 106 
     | 
    
         
            +
                            .enter().append("text")
         
     | 
| 
      
 107 
     | 
    
         
            +
                              .attr("class", (d) => d.children ? "label dir" : "label file")
         
     | 
| 
      
 108 
     | 
    
         
            +
                              .attr("fill-opacity", (d) => d.depth <= 1 ? 1 : 0)
         
     | 
| 
      
 109 
     | 
    
         
            +
                              .attr("display", (d) => d.depth <= 1 ? "inline" : "none")
         
     | 
| 
      
 110 
     | 
    
         
            +
                              .text((d) => d.data.name);
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                          var node = svg_#{id}.selectAll("circle,text");
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                          function zoom(d) {
         
     | 
| 
      
 115 
     | 
    
         
            +
                            var focus0 = focus; focus = d;
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                            var transition = d3.transition()
         
     | 
| 
      
 118 
     | 
    
         
            +
                                .duration(d3.event.altKey ? 7500 : 750)
         
     | 
| 
      
 119 
     | 
    
         
            +
                                .tween("zoom", function(d) {
         
     | 
| 
      
 120 
     | 
    
         
            +
                                  var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
         
     | 
| 
      
 121 
     | 
    
         
            +
                                  return (t) => zoomTo(i(t));
         
     | 
| 
      
 122 
     | 
    
         
            +
                                });
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                            transition.selectAll("text")
         
     | 
| 
      
 125 
     | 
    
         
            +
                              .filter(function(d) { return d && d.parent === focus || this.style.display === "inline"; })
         
     | 
| 
      
 126 
     | 
    
         
            +
                                .attr("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
         
     | 
| 
      
 127 
     | 
    
         
            +
                                .on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
         
     | 
| 
      
 128 
     | 
    
         
            +
                                .on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
         
     | 
| 
      
 129 
     | 
    
         
            +
                          }
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                          function zoomTo(v) {
         
     | 
| 
      
 132 
     | 
    
         
            +
                            var k = diameter / v[2]; view = v;
         
     | 
| 
      
 133 
     | 
    
         
            +
                            node.attr("transform", (d) => "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")");
         
     | 
| 
      
 134 
     | 
    
         
            +
                            circle.attr("r", (d) => d.r * k);
         
     | 
| 
      
 135 
     | 
    
         
            +
                          }
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                          zoomTo([root.x, root.y, root.r * 2]);
         
     | 
| 
      
 138 
     | 
    
         
            +
                          d3.select("##{id}").on("click", () => zoom(root));
         
     | 
| 
      
 139 
     | 
    
         
            +
                        }
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                        redraw();
         
     | 
| 
      
 142 
     | 
    
         
            +
                        window.addEventListener("resize", redraw);
         
     | 
| 
      
 143 
     | 
    
         
            +
                      JS
         
     | 
| 
      
 144 
     | 
    
         
            +
                    end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                    class Sigils < CircleMap
         
     | 
| 
      
 147 
     | 
    
         
            +
                      extend T::Sig
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                      sig { params(id: String, sigils_tree: FileTree).void }
         
     | 
| 
      
 150 
     | 
    
         
            +
                      def initialize(id, sigils_tree)
         
     | 
| 
      
 151 
     | 
    
         
            +
                        @scores = T.let({}, T::Hash[FileTree::Node, Float])
         
     | 
| 
      
 152 
     | 
    
         
            +
                        @strictnesses = T.let({}, T::Hash[FileTree::Node, T.nilable(String)])
         
     | 
| 
      
 153 
     | 
    
         
            +
                        @sigils_tree = sigils_tree
         
     | 
| 
      
 154 
     | 
    
         
            +
                        super(id, sigils_tree.roots.map { |r| tree_node_to_json(r) })
         
     | 
| 
      
 155 
     | 
    
         
            +
                      end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                      sig { params(node: FileTree::Node).returns(T::Hash[Symbol, T.untyped]) }
         
     | 
| 
      
 158 
     | 
    
         
            +
                      def tree_node_to_json(node)
         
     | 
| 
      
 159 
     | 
    
         
            +
                        if node.children.empty?
         
     | 
| 
      
 160 
     | 
    
         
            +
                          return { name: node.name, strictness: tree_node_strictness(node) }
         
     | 
| 
      
 161 
     | 
    
         
            +
                        end
         
     | 
| 
      
 162 
     | 
    
         
            +
                        {
         
     | 
| 
      
 163 
     | 
    
         
            +
                          name: node.name,
         
     | 
| 
      
 164 
     | 
    
         
            +
                          children: node.children.values.map { |n| tree_node_to_json(n) },
         
     | 
| 
      
 165 
     | 
    
         
            +
                          score: tree_node_score(node),
         
     | 
| 
      
 166 
     | 
    
         
            +
                        }
         
     | 
| 
      
 167 
     | 
    
         
            +
                      end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                      sig { params(node: FileTree::Node).returns(T.nilable(String)) }
         
     | 
| 
      
 170 
     | 
    
         
            +
                      def tree_node_strictness(node)
         
     | 
| 
      
 171 
     | 
    
         
            +
                        prefix = @sigils_tree.strip_prefix
         
     | 
| 
      
 172 
     | 
    
         
            +
                        path = node.path
         
     | 
| 
      
 173 
     | 
    
         
            +
                        path = "#{prefix}/#{path}" if prefix
         
     | 
| 
      
 174 
     | 
    
         
            +
                        @strictnesses[node] ||= Spoom::Sorbet::Sigils.file_strictness(path)
         
     | 
| 
      
 175 
     | 
    
         
            +
                      end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                      sig { params(node: FileTree::Node).returns(Float) }
         
     | 
| 
      
 178 
     | 
    
         
            +
                      def tree_node_score(node)
         
     | 
| 
      
 179 
     | 
    
         
            +
                        unless @scores.key?(node)
         
     | 
| 
      
 180 
     | 
    
         
            +
                          if node.name =~ /\.rbi?$/
         
     | 
| 
      
 181 
     | 
    
         
            +
                            case tree_node_strictness(node)
         
     | 
| 
      
 182 
     | 
    
         
            +
                            when "true", "strict", "strong"
         
     | 
| 
      
 183 
     | 
    
         
            +
                              @scores[node] = 1.0
         
     | 
| 
      
 184 
     | 
    
         
            +
                            end
         
     | 
| 
      
 185 
     | 
    
         
            +
                          elsif !node.children.empty?
         
     | 
| 
      
 186 
     | 
    
         
            +
                            @scores[node] = node.children.values.sum { |n| tree_node_score(n) } / node.children.size.to_f
         
     | 
| 
      
 187 
     | 
    
         
            +
                          end
         
     | 
| 
      
 188 
     | 
    
         
            +
                        end
         
     | 
| 
      
 189 
     | 
    
         
            +
                        @scores[node] || 0.0
         
     | 
| 
      
 190 
     | 
    
         
            +
                      end
         
     | 
| 
      
 191 
     | 
    
         
            +
                    end
         
     | 
| 
      
 192 
     | 
    
         
            +
                  end
         
     | 
| 
      
 193 
     | 
    
         
            +
                end
         
     | 
| 
      
 194 
     | 
    
         
            +
              end
         
     | 
| 
      
 195 
     | 
    
         
            +
            end
         
     |