spoom 1.1.11 → 1.1.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b239fa524447d3a3cd6290f8fa373cdaf9a1b3b76bacbbf290519820fe31f542
4
- data.tar.gz: 389b772cafcc6658f6033707ec1696cc1221aab64efc8c0c4bef984e672e1ea4
3
+ metadata.gz: ed6d04e01efc1c6c8c00df77ad340f189a277aebf3afbd64edbd89f6145541f2
4
+ data.tar.gz: 1935557277bcfdb54b4ab0d389ab5a72fb5b2d00a6631f6c787dae9d83dac7c4
5
5
  SHA512:
6
- metadata.gz: df641a876db23dd5aac45965adbae953ba43ae2fb20720498d6760843212a3541b4d3396401ad9cccb99db58830d3bec7124e32a27372496c61fd896517be126
7
- data.tar.gz: 4f1b7fe4cd814333c449820bbd24f4fd3ca4883986b3d6f3c0c6f59dada06e136114930a9e8e20a72beb85854ef5f240d7dd6d6c0c5c0c28f5b70fc01185045a
6
+ metadata.gz: 781d3bb6443c518fc1d4334e5c4ac17a09372ba9115f024bcdceb5cf15af4d4524d1e4cb465963ce5d23aaa07ebd5ab2114ebe81b4eea3fffe27942295e21b9f
7
+ data.tar.gz: 3427fdfe5cd483609fbbe6618de2f8a536f8bf0727a6865e44c2c4c9b74f9303c0fdcfdefd44bdd547d16e6766f0f09fa49c1e8cd991d0efcffaa37f3c9c7609
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gemspec
7
7
 
8
8
  group :development do
9
9
  gem "pry-byebug"
10
+ gem "ruby-lsp"
10
11
  gem "rubocop-shopify", require: false
11
12
  gem "rubocop-sorbet", require: false
12
13
  gem "tapioca", require: false
data/README.md CHANGED
@@ -121,6 +121,12 @@ Hide the `Errors: X` at the end of the list:
121
121
  $ spoom tc --no-count
122
122
  ```
123
123
 
124
+ List only the errors comming from specific directories or files:
125
+
126
+ ```
127
+ $ spoom tc file1.rb path1/ path2/
128
+ ```
129
+
124
130
  #### Typing coverage
125
131
 
126
132
  Show metrics about the project contents and the typing coverage:
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
+
3
4
  require "bundler/gem_tasks"
4
5
  require "rake/testtask"
5
6
 
@@ -1,8 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'find'
5
- require 'open3'
4
+ require "find"
5
+ require "open3"
6
6
 
7
7
  module Spoom
8
8
  module Cli
@@ -85,7 +85,6 @@ module Spoom
85
85
 
86
86
  error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
87
87
  result = Sorbet.srb_tc(
88
- "--no-error-sections",
89
88
  "--error-url-base=#{error_url_base}",
90
89
  path: exec_path,
91
90
  capture_err: true,
@@ -108,17 +107,22 @@ module Spoom
108
107
 
109
108
  errors = Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
110
109
 
111
- files_with_errors = errors.map do |err|
112
- path = File.expand_path(err.file)
110
+ all_files = errors.flat_map do |err|
111
+ [err.file, *err.files_from_error_sections]
112
+ end
113
+
114
+ files_with_errors = all_files.map do |file|
115
+ path = File.expand_path(file)
113
116
  next unless path.start_with?(directory)
114
117
  next unless File.file?(path)
115
118
  next unless files_to_bump.include?(path)
119
+
116
120
  path
117
121
  end.compact.uniq
118
122
 
119
123
  undo_changes(files_with_errors, from)
120
124
 
121
- say("Found #{errors.length} type checking error#{'s' if errors.length > 1}") if options[:count_errors]
125
+ say("Found #{errors.length} type checking error#{"s" if errors.length > 1}") if options[:count_errors]
122
126
 
123
127
  files_changed = files_to_bump - files_with_errors
124
128
  print_changes(files_changed, command: cmd, from: from, to: to, dry: dry, path: exec_path)
@@ -134,7 +138,7 @@ module Spoom
134
138
  end
135
139
  message = StringIO.new
136
140
  message << (dry ? "Can bump" : "Bumped")
137
- message << " `#{files.size}` file#{'s' if files.size > 1}"
141
+ message << " `#{files.size}` file#{"s" if files.size > 1}"
138
142
  message << " from `#{from}` to `#{to}`:"
139
143
  say(message.string)
140
144
  files.each do |file|
@@ -1,8 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative '../coverage'
5
- require_relative '../timeline'
4
+ require_relative "../coverage"
5
+ require_relative "../timeline"
6
6
 
7
7
  module Spoom
8
8
  module Cli
@@ -27,6 +27,7 @@ module Spoom
27
27
 
28
28
  save_dir = options[:save]
29
29
  return unless save_dir
30
+
30
31
  FileUtils.mkdir_p(save_dir)
31
32
  file = "#{save_dir}/#{snapshot.commit_sha || snapshot.timestamp}.json"
32
33
  File.write(file, snapshot.to_json)
@@ -85,7 +86,7 @@ module Spoom
85
86
 
86
87
  ticks.each_with_index do |sha, i|
87
88
  date = Spoom::Git.commit_time(sha, path: path)
88
- say("Analyzing commit `#{sha}` - #{date&.strftime('%F')} (#{i + 1} / #{ticks.size})")
89
+ say("Analyzing commit `#{sha}` - #{date&.strftime("%F")} (#{i + 1} / #{ticks.size})")
89
90
 
90
91
  Spoom::Git.checkout(sha, path: path)
91
92
 
@@ -93,6 +94,7 @@ module Spoom
93
94
  if options[:bundle_install]
94
95
  Bundler.with_unbundled_env do
95
96
  next unless bundle_install(path, sha)
97
+
96
98
  snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
97
99
  end
98
100
  else
@@ -104,6 +106,7 @@ module Spoom
104
106
  say("\n")
105
107
 
106
108
  next unless save_dir
109
+
107
110
  file = "#{save_dir}/#{sha}.json"
108
111
  File.write(file, snapshot.to_json)
109
112
  say(" Snapshot data saved under `#{file}`\n\n")
@@ -161,9 +164,9 @@ module Spoom
161
164
  say_error("No report file to open `#{file}`")
162
165
  say_error(<<~ERR, status: nil)
163
166
 
164
- If you already generated a report under another name use #{blue('spoom coverage open PATH')}.
167
+ If you already generated a report under another name use #{blue("spoom coverage open PATH")}.
165
168
 
166
- To generate a report run #{blue('spoom coverage report')}.
169
+ To generate a report run #{blue("spoom coverage report")}.
167
170
  ERR
168
171
  exit(1)
169
172
  end
@@ -174,6 +177,7 @@ module Spoom
174
177
  no_commands do
175
178
  def parse_time(string, option)
176
179
  return nil unless string
180
+
177
181
  Time.parse(string)
178
182
  rescue ArgumentError
179
183
  say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
@@ -196,9 +200,9 @@ module Spoom
196
200
  say_error("No snapshot files found in `#{file}`")
197
201
  say_error(<<~ERR, status: nil)
198
202
 
199
- If you already generated snapshot files under another directory use #{blue('spoom coverage report PATH')}.
203
+ If you already generated snapshot files under another directory use #{blue("spoom coverage report PATH")}.
200
204
 
201
- To generate snapshot files run #{blue('spoom coverage timeline --save-dir spoom_data')}.
205
+ To generate snapshot files run #{blue("spoom coverage timeline --save")}.
202
206
  ERR
203
207
  end
204
208
  end
@@ -60,8 +60,8 @@ module Spoom
60
60
  unless in_sorbet_project?
61
61
  say_error(
62
62
  "not in a Sorbet project (`#{sorbet_config_file}` not found)\n\n" \
63
- "When running spoom from another path than the project's root, " \
64
- "use `--path PATH` to specify the path to the root."
63
+ "When running spoom from another path than the project's root, " \
64
+ "use `--path PATH` to specify the path to the root."
65
65
  )
66
66
  Kernel.exit(1)
67
67
  end
@@ -116,9 +116,9 @@ module Spoom
116
116
  word = StringIO.new
117
117
  in_ticks = T.let(false, T::Boolean)
118
118
  string.chars.each do |c|
119
- if c == '`' && !in_ticks
119
+ if c == "`" && !in_ticks
120
120
  in_ticks = true
121
- elsif c == '`' && in_ticks
121
+ elsif c == "`" && in_ticks
122
122
  in_ticks = false
123
123
  res << colorize(word.string, HIGHLIGHT_COLOR)
124
124
  word = StringIO.new
@@ -135,6 +135,7 @@ module Spoom
135
135
  sig { params(string: String, color: Color).returns(String) }
136
136
  def colorize(string, *color)
137
137
  return string unless color?
138
+
138
139
  T.unsafe(self).set_color(string, *color)
139
140
  end
140
141
 
data/lib/spoom/cli/lsp.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'shellwords'
4
+ require "shellwords"
5
5
 
6
6
  require_relative "../sorbet/lsp"
7
7
 
@@ -28,6 +28,7 @@ module Spoom
28
28
  Dir["**/*.rb"].each do |file|
29
29
  res = client.document_symbols(to_uri(file))
30
30
  next if res.empty?
31
+
31
32
  say("Symbols from `#{file}`:")
32
33
  printer.print_objects(res)
33
34
  end
data/lib/spoom/cli/run.rb CHANGED
@@ -22,7 +22,8 @@ module Spoom
22
22
  option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
23
23
  option :count, type: :boolean, default: true, desc: "Show errors count"
24
24
  option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
25
- def tc(*arg)
25
+ option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
26
+ def tc(*paths_to_select)
26
27
  in_sorbet_project!
27
28
 
28
29
  path = exec_path
@@ -36,7 +37,7 @@ module Spoom
36
37
 
37
38
  unless limit || code || sort
38
39
  result = T.unsafe(Spoom::Sorbet).srb_tc(
39
- *arg,
40
+ *options[:sorbet_options].split(" "),
40
41
  path: path,
41
42
  capture_err: false,
42
43
  sorbet_bin: sorbet
@@ -47,8 +48,10 @@ module Spoom
47
48
  exit(result.status)
48
49
  end
49
50
 
51
+ error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
50
52
  result = T.unsafe(Spoom::Sorbet).srb_tc(
51
- *arg,
53
+ *options[:sorbet_options].split(" "),
54
+ "--error-url-base=#{error_url_base}",
52
55
  path: path,
53
56
  capture_err: true,
54
57
  sorbet_bin: sorbet
@@ -61,9 +64,17 @@ module Spoom
61
64
  exit(0)
62
65
  end
63
66
 
64
- errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err)
67
+ errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
65
68
  errors_count = errors.size
66
69
 
70
+ errors = errors.select { |e| e.code == code } if code
71
+
72
+ unless paths_to_select.empty?
73
+ errors.select! do |error|
74
+ paths_to_select.any? { |path_to_select| error.file&.start_with?(path_to_select) }
75
+ end
76
+ end
77
+
67
78
  errors = case sort
68
79
  when SORT_CODE
69
80
  Spoom::Sorbet::Errors.sort_errors_by_code(errors)
@@ -73,7 +84,6 @@ module Spoom
73
84
  errors # preserve natural sort
74
85
  end
75
86
 
76
- errors = errors.select { |e| e.code == code } if code
77
87
  errors = T.must(errors.slice(0, limit)) if limit
78
88
 
79
89
  lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
@@ -110,7 +120,7 @@ module Spoom
110
120
  cyan = T.let(false, T::Boolean)
111
121
  word = StringIO.new
112
122
  message.chars.each do |c|
113
- if c == '`'
123
+ if c == "`"
114
124
  cyan = !cyan
115
125
  next
116
126
  end
data/lib/spoom/cli.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  require "thor"
5
5
 
6
- require_relative 'cli/helper'
6
+ require_relative "cli/helper"
7
7
 
8
8
  require_relative "cli/bump"
9
9
  require_relative "cli/config"
@@ -20,7 +20,7 @@ module Spoom
20
20
  class_option :color, type: :boolean, default: true, desc: "Use colors"
21
21
  class_option :path, type: :string, default: ".", aliases: :p, desc: "Run spoom in a specific path"
22
22
 
23
- map T.unsafe(%w[--version -v] => :__print_version)
23
+ map T.unsafe(["--version", "-v"] => :__print_version)
24
24
 
25
25
  desc "bump", "Bump Sorbet sigils from `false` to `true` when no errors"
26
26
  subcommand "bump", Spoom::Cli::Bump
@@ -71,8 +71,10 @@ module Spoom
71
71
 
72
72
  # Utils
73
73
 
74
- def self.exit_on_failure?
75
- true
74
+ class << self
75
+ def exit_on_failure?
76
+ true
77
+ end
76
78
  end
77
79
  end
78
80
  end
@@ -0,0 +1,219 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "fileutils"
5
+ require "open3"
6
+
7
+ module Spoom
8
+ # An abstraction to a Ruby project context
9
+ #
10
+ # A context maps to a directory in the file system.
11
+ # It is used to manipulate files and run commands in the context of this directory.
12
+ class Context
13
+ extend T::Sig
14
+
15
+ # The absolute path to the directory this context is about
16
+ sig { returns(String) }
17
+ attr_reader :absolute_path
18
+
19
+ class << self
20
+ extend T::Sig
21
+
22
+ # Create a new context in the system's temporary directory
23
+ #
24
+ # `name` is used as prefix to the temporary directory name.
25
+ # The directory will be created if it doesn't exist.
26
+ sig { params(name: T.nilable(String)).returns(T.attached_class) }
27
+ def mktmp!(name = nil)
28
+ new(::Dir.mktmpdir(name))
29
+ end
30
+ end
31
+
32
+ # Create a new context about `absolute_path`
33
+ #
34
+ # The directory will not be created if it doesn't exist.
35
+ # Call `#make!` to create it.
36
+ sig { params(absolute_path: String).void }
37
+ def initialize(absolute_path)
38
+ @absolute_path = T.let(::File.expand_path(absolute_path), String)
39
+ end
40
+
41
+ # Returns the absolute path to `relative_path` in the context's directory
42
+ sig { params(relative_path: String).returns(String) }
43
+ def absolute_path_to(relative_path)
44
+ File.join(@absolute_path, relative_path)
45
+ end
46
+
47
+ # File System
48
+
49
+ # Create the context directory at `absolute_path`
50
+ sig { void }
51
+ def mkdir!
52
+ FileUtils.rm_rf(@absolute_path)
53
+ FileUtils.mkdir_p(@absolute_path)
54
+ end
55
+
56
+ # List all files in this context matching `pattern`
57
+ sig { params(pattern: String).returns(T::Array[String]) }
58
+ def glob(pattern = "**/*")
59
+ Dir.glob(absolute_path_to(pattern)).map do |path|
60
+ Pathname.new(path).relative_path_from(@absolute_path).to_s
61
+ end.sort
62
+ end
63
+
64
+ # List all files at the top level of this context directory
65
+ sig { returns(T::Array[String]) }
66
+ def list
67
+ glob("*")
68
+ end
69
+
70
+ # Does `relative_path` point to an existing file in this context directory?
71
+ sig { params(relative_path: String).returns(T::Boolean) }
72
+ def file?(relative_path)
73
+ File.file?(absolute_path_to(relative_path))
74
+ end
75
+
76
+ # Return the contents of the file at `relative_path` in this context directory
77
+ #
78
+ # Will raise if the file doesn't exist.
79
+ sig { params(relative_path: String).returns(String) }
80
+ def read(relative_path)
81
+ File.read(absolute_path_to(relative_path))
82
+ end
83
+
84
+ # Write `contents` in the file at `relative_path` in this context directory
85
+ #
86
+ # Append to the file if `append` is true.
87
+ sig { params(relative_path: String, contents: String, append: T::Boolean).void }
88
+ def write!(relative_path, contents = "", append: false)
89
+ absolute_path = absolute_path_to(relative_path)
90
+ FileUtils.mkdir_p(File.dirname(absolute_path))
91
+ File.write(absolute_path, contents, mode: append ? "a" : "w")
92
+ end
93
+
94
+ # Remove the path at `relative_path` (recursive + force) in this context directory
95
+ sig { params(relative_path: String).void }
96
+ def remove!(relative_path)
97
+ FileUtils.rm_rf(absolute_path_to(relative_path))
98
+ end
99
+
100
+ # Move the file or directory from `from_relative_path` to `to_relative_path`
101
+ sig { params(from_relative_path: String, to_relative_path: String).void }
102
+ def move!(from_relative_path, to_relative_path)
103
+ destination_path = absolute_path_to(to_relative_path)
104
+ FileUtils.mkdir_p(File.dirname(destination_path))
105
+ FileUtils.mv(absolute_path_to(from_relative_path), destination_path)
106
+ end
107
+
108
+ # Delete this context and its content
109
+ #
110
+ # Warning: it will `rm -rf` the context directory on the file system.
111
+ sig { void }
112
+ def destroy!
113
+ FileUtils.rm_rf(@absolute_path)
114
+ end
115
+
116
+ # Execution
117
+
118
+ # Run a command in this context directory
119
+ sig { params(command: String, capture_err: T::Boolean).returns(ExecResult) }
120
+ def exec(command, capture_err: true)
121
+ Bundler.with_unbundled_env do
122
+ opts = T.let({ chdir: @absolute_path }, T::Hash[Symbol, T.untyped])
123
+
124
+ if capture_err
125
+ out, err, status = Open3.capture3(command, opts)
126
+ ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
127
+ else
128
+ out, status = Open3.capture2(command, opts)
129
+ ExecResult.new(out: out, err: "", status: T.must(status.success?), exit_code: T.must(status.exitstatus))
130
+ end
131
+ end
132
+ end
133
+
134
+ # Bundle
135
+
136
+ # Read the `contents` of the Gemfile in this context directory
137
+ sig { returns(T.nilable(String)) }
138
+ def read_gemfile
139
+ read("Gemfile")
140
+ end
141
+
142
+ # Set the `contents` of the Gemfile in this context directory
143
+ sig { params(contents: String, append: T::Boolean).void }
144
+ def write_gemfile!(contents, append: false)
145
+ write!("Gemfile", contents, append: append)
146
+ end
147
+
148
+ # Run a command with `bundle` in this context directory
149
+ sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
150
+ def bundle(command, version: nil)
151
+ command = "_#{version}_ #{command}" if version
152
+ exec("bundle #{command}")
153
+ end
154
+
155
+ # Run `bundle install` in this context directory
156
+ sig { params(version: T.nilable(String)).returns(ExecResult) }
157
+ def bundle_install!(version: nil)
158
+ bundle("install", version: version)
159
+ end
160
+
161
+ # Run a command `bundle exec` in this context directory
162
+ sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
163
+ def bundle_exec(command, version: nil)
164
+ bundle("exec #{command}", version: version)
165
+ end
166
+
167
+ # Git
168
+
169
+ # Run a command prefixed by `git` in this context directory
170
+ sig { params(command: String).returns(ExecResult) }
171
+ def git(command)
172
+ exec("git #{command}")
173
+ end
174
+
175
+ # Run `git init` in this context directory
176
+ sig { params(branch: String).void }
177
+ def git_init!(branch: "main")
178
+ git("init -q -b #{branch}")
179
+ end
180
+
181
+ # Run `git checkout` in this context directory
182
+ sig { params(ref: String).returns(ExecResult) }
183
+ def git_checkout!(ref: "main")
184
+ git("checkout #{ref}")
185
+ end
186
+
187
+ # Get the current git branch in this context directory
188
+ sig { returns(T.nilable(String)) }
189
+ def git_current_branch
190
+ Spoom::Git.current_branch(path: @absolute_path)
191
+ end
192
+
193
+ # Sorbet
194
+
195
+ # Run `bundle exec srb` in this context directory
196
+ sig { params(command: String).returns(ExecResult) }
197
+ def srb(command)
198
+ bundle_exec("srb #{command}")
199
+ end
200
+
201
+ # Read the contents of `sorbet/config` in this context directory
202
+ sig { returns(String) }
203
+ def read_sorbet_config
204
+ read(Spoom::Sorbet::CONFIG_PATH)
205
+ end
206
+
207
+ # Set the `contents` of `sorbet/config` in this context directory
208
+ sig { params(contents: String, append: T::Boolean).void }
209
+ def write_sorbet_config!(contents, append: false)
210
+ write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
211
+ end
212
+
213
+ # Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
214
+ sig { params(relative_path: String).returns(T.nilable(String)) }
215
+ def read_file_strictness(relative_path)
216
+ Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
217
+ end
218
+ end
219
+ end
@@ -19,14 +19,18 @@ module Spoom
19
19
  @data = data
20
20
  end
21
21
 
22
- sig { returns(String) }
23
- def self.header_style
24
- ""
25
- end
26
-
27
- sig { returns(String) }
28
- def self.header_script
29
- ""
22
+ class << self
23
+ extend T::Sig
24
+
25
+ sig { returns(String) }
26
+ def header_style
27
+ ""
28
+ end
29
+
30
+ sig { returns(String) }
31
+ def header_script
32
+ ""
33
+ end
30
34
  end
31
35
 
32
36
  sig { returns(String) }
@@ -7,50 +7,52 @@ module Spoom
7
7
  module Coverage
8
8
  module D3
9
9
  class CircleMap < Base
10
- extend T::Sig
10
+ class << self
11
+ extend T::Sig
11
12
 
12
- sig { returns(String) }
13
- def self.header_style
14
- <<~CSS
15
- .node {
16
- cursor: pointer;
17
- }
13
+ sig { returns(String) }
14
+ def header_style
15
+ <<~CSS
16
+ .node {
17
+ cursor: pointer;
18
+ }
18
19
 
19
- .node:hover {
20
- stroke: #333;
21
- stroke-width: 1px;
22
- }
20
+ .node:hover {
21
+ stroke: #333;
22
+ stroke-width: 1px;
23
+ }
23
24
 
24
- .label.dir {
25
- fill: #333;
26
- }
25
+ .label.dir {
26
+ fill: #333;
27
+ }
27
28
 
28
- .label.file {
29
- font: 12px Arial, sans-serif;
30
- }
29
+ .label.file {
30
+ font: 12px Arial, sans-serif;
31
+ }
31
32
 
32
- .node.root, .node.file {
33
- pointer-events: none;
34
- }
35
- CSS
36
- end
33
+ .node.root, .node.file {
34
+ pointer-events: none;
35
+ }
36
+ CSS
37
+ end
37
38
 
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
- }
39
+ sig { returns(String) }
40
+ def header_script
41
+ <<~JS
42
+ function treeHeight(root, height = 0) {
43
+ height += 1;
44
+ if (root.children && root.children.length > 0)
45
+ return Math.max(...root.children.map(child => treeHeight(child, height)));
46
+ else
47
+ return height;
48
+ }
48
49
 
49
- function tooltipMap(d) {
50
- moveTooltip(d)
51
- .html("<b>" + d.data.name + "</b>")
52
- }
53
- JS
50
+ function tooltipMap(d) {
51
+ moveTooltip(d)
52
+ .html("<b>" + d.data.name + "</b>")
53
+ }
54
+ JS
55
+ end
54
56
  end
55
57
 
56
58
  sig { override.returns(String) }
@@ -159,6 +161,7 @@ module Spoom
159
161
  if node.children.empty?
160
162
  return { name: node.name, strictness: tree_node_strictness(node) }
161
163
  end
164
+
162
165
  {
163
166
  name: node.name,
164
167
  children: node.children.values.map { |n| tree_node_to_json(n) },