spoom 1.1.11 → 1.1.13

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.
@@ -109,7 +109,7 @@ module Spoom
109
109
  abstract!
110
110
 
111
111
  sig { void }
112
- def initialize; end
112
+ def initialize; end # rubocop:disable Lint/MissingSuper
113
113
 
114
114
  sig { override.returns(String) }
115
115
  def html
@@ -136,17 +136,17 @@ module Spoom
136
136
 
137
137
  sig { returns(D3::Pie::Sigils) }
138
138
  def pie_sigils
139
- D3::Pie::Sigils.new('pie_sigils', 'Sigils', snapshot)
139
+ D3::Pie::Sigils.new("pie_sigils", "Sigils", snapshot)
140
140
  end
141
141
 
142
142
  sig { returns(D3::Pie::Calls) }
143
143
  def pie_calls
144
- D3::Pie::Calls.new('pie_calls', 'Calls', snapshot)
144
+ D3::Pie::Calls.new("pie_calls", "Calls", snapshot)
145
145
  end
146
146
 
147
147
  sig { returns(D3::Pie::Sigs) }
148
148
  def pie_sigs
149
- D3::Pie::Sigs.new('pie_sigs', 'Sigs', snapshot)
149
+ D3::Pie::Sigs.new("pie_sigs", "Sigs", snapshot)
150
150
  end
151
151
  end
152
152
 
@@ -226,7 +226,7 @@ module Spoom
226
226
  extend T::Sig
227
227
 
228
228
  sig { params(sorbet_intro_commit: T.nilable(String), sorbet_intro_date: T.nilable(Time)).void }
229
- def initialize(sorbet_intro_commit: nil, sorbet_intro_date: nil)
229
+ def initialize(sorbet_intro_commit: nil, sorbet_intro_date: nil) # rubocop:disable Lint/MissingSuper
230
230
  @sorbet_intro_commit = sorbet_intro_commit
231
231
  @sorbet_intro_date = sorbet_intro_date
232
232
  end
@@ -235,7 +235,7 @@ module Spoom
235
235
  def erb
236
236
  <<~ERB
237
237
  <div class="text-center" style="margin-top: 30px">
238
- Typchecked by Sorbet since <b>#{@sorbet_intro_date&.strftime('%F')}</b>
238
+ Typchecked by Sorbet since <b>#{@sorbet_intro_date&.strftime("%F")}</b>
239
239
  (commit <b>#{@sorbet_intro_commit}</b>).
240
240
  </div>
241
241
  ERB
@@ -22,6 +22,9 @@ module Spoom
22
22
  prop :calls_untyped, Integer, default: 0
23
23
  prop :calls_typed, Integer, default: 0
24
24
  prop :sigils, T::Hash[String, Integer], default: Hash.new(0)
25
+ prop :methods_with_sig_excluding_rbis, Integer, default: 0
26
+ prop :methods_without_sig_excluding_rbis, Integer, default: 0
27
+ prop :sigils_excluding_rbis, T::Hash[String, Integer], default: Hash.new(0)
25
28
 
26
29
  # The strictness name as found in the Sorbet metrics file
27
30
  STRICTNESSES = T.let(["ignore", "false", "true", "strict", "strong", "stdlib"].freeze, T::Array[String])
@@ -32,44 +35,60 @@ module Spoom
32
35
  printer.print_snapshot(self)
33
36
  end
34
37
 
35
- sig { params(json: String).returns(Snapshot) }
36
- def self.from_json(json)
37
- from_obj(JSON.parse(json))
38
+ sig { params(arg: T.untyped).returns(String) }
39
+ def to_json(*arg)
40
+ serialize.to_json(*arg)
38
41
  end
39
42
 
40
- sig { params(obj: T::Hash[String, T.untyped]).returns(Snapshot) }
41
- def self.from_obj(obj)
42
- snapshot = Snapshot.new
43
- snapshot.timestamp = obj.fetch("timestamp", 0)
44
- snapshot.version_static = obj.fetch("version_static", nil)
45
- snapshot.version_runtime = obj.fetch("version_runtime", nil)
46
- snapshot.duration = obj.fetch("duration", 0)
47
- snapshot.commit_sha = obj.fetch("commit_sha", nil)
48
- snapshot.commit_timestamp = obj.fetch("commit_timestamp", nil)
49
- snapshot.files = obj.fetch("files", 0)
50
- snapshot.rbi_files = obj.fetch("rbi_files", 0)
51
- snapshot.modules = obj.fetch("modules", 0)
52
- snapshot.classes = obj.fetch("classes", 0)
53
- snapshot.singleton_classes = obj.fetch("singleton_classes", 0)
54
- snapshot.methods_with_sig = obj.fetch("methods_with_sig", 0)
55
- snapshot.methods_without_sig = obj.fetch("methods_without_sig", 0)
56
- snapshot.calls_typed = obj.fetch("calls_typed", 0)
57
- snapshot.calls_untyped = obj.fetch("calls_untyped", 0)
58
-
59
- sigils = obj.fetch("sigils", {})
60
- if sigils
61
- Snapshot::STRICTNESSES.each do |strictness|
62
- next unless sigils.key?(strictness)
63
- snapshot.sigils[strictness] = sigils[strictness]
64
- end
43
+ class << self
44
+ extend T::Sig
45
+
46
+ sig { params(json: String).returns(Snapshot) }
47
+ def from_json(json)
48
+ from_obj(JSON.parse(json))
65
49
  end
66
50
 
67
- snapshot
68
- end
51
+ sig { params(obj: T::Hash[String, T.untyped]).returns(Snapshot) }
52
+ def from_obj(obj)
53
+ snapshot = Snapshot.new
54
+ snapshot.timestamp = obj.fetch("timestamp", 0)
55
+ snapshot.version_static = obj.fetch("version_static", nil)
56
+ snapshot.version_runtime = obj.fetch("version_runtime", nil)
57
+ snapshot.duration = obj.fetch("duration", 0)
58
+ snapshot.commit_sha = obj.fetch("commit_sha", nil)
59
+ snapshot.commit_timestamp = obj.fetch("commit_timestamp", nil)
60
+ snapshot.files = obj.fetch("files", 0)
61
+ snapshot.rbi_files = obj.fetch("rbi_files", 0)
62
+ snapshot.modules = obj.fetch("modules", 0)
63
+ snapshot.classes = obj.fetch("classes", 0)
64
+ snapshot.singleton_classes = obj.fetch("singleton_classes", 0)
65
+ snapshot.methods_with_sig = obj.fetch("methods_with_sig", 0)
66
+ snapshot.methods_without_sig = obj.fetch("methods_without_sig", 0)
67
+ snapshot.calls_typed = obj.fetch("calls_typed", 0)
68
+ snapshot.calls_untyped = obj.fetch("calls_untyped", 0)
69
+ snapshot.methods_with_sig_excluding_rbis = obj.fetch("methods_with_sig_excluding_rbis", 0)
70
+ snapshot.methods_without_sig_excluding_rbis = obj.fetch("methods_without_sig_excluding_rbis", 0)
69
71
 
70
- sig { params(arg: T.untyped).returns(String) }
71
- def to_json(*arg)
72
- serialize.to_json(*arg)
72
+ sigils = obj.fetch("sigils", {})
73
+ if sigils
74
+ Snapshot::STRICTNESSES.each do |strictness|
75
+ next unless sigils.key?(strictness)
76
+
77
+ snapshot.sigils[strictness] = sigils[strictness]
78
+ end
79
+ end
80
+
81
+ sigils_excluding_rbis = obj.fetch("sigils_excluding_rbis", {})
82
+ if sigils_excluding_rbis
83
+ Snapshot::STRICTNESSES.each do |strictness|
84
+ next unless sigils_excluding_rbis.key?(strictness)
85
+
86
+ snapshot.sigils_excluding_rbis[strictness] = sigils_excluding_rbis[strictness]
87
+ end
88
+ end
89
+
90
+ snapshot
91
+ end
73
92
  end
74
93
  end
75
94
 
@@ -79,6 +98,7 @@ module Spoom
79
98
  sig { params(snapshot: Snapshot).void }
80
99
  def print_snapshot(snapshot)
81
100
  methods = snapshot.methods_with_sig + snapshot.methods_without_sig
101
+ methods_excluding_rbis = snapshot.methods_with_sig_excluding_rbis + snapshot.methods_without_sig_excluding_rbis
82
102
  calls = snapshot.calls_typed + snapshot.calls_untyped
83
103
 
84
104
  if snapshot.version_static || snapshot.version_runtime
@@ -88,10 +108,12 @@ module Spoom
88
108
  end
89
109
  printl("Content:")
90
110
  indent
91
- printl("files: #{snapshot.files} (including #{snapshot.rbi_files} RBIs)")
111
+ printl("files: #{snapshot.files}")
112
+ printl("files excluding rbis: #{snapshot.files - snapshot.rbi_files}")
92
113
  printl("modules: #{snapshot.modules}")
93
114
  printl("classes: #{snapshot.classes - snapshot.singleton_classes}")
94
115
  printl("methods: #{methods}")
116
+ printl("methods excluding rbis: #{methods_excluding_rbis}")
95
117
  dedent
96
118
  printn
97
119
  printl("Sigils:")
@@ -104,6 +126,13 @@ module Spoom
104
126
  }
105
127
  print_map(methods_map, methods)
106
128
  printn
129
+ printl("Methods excluding RBIs")
130
+ methods_excluding_rbis_map = {
131
+ "with signature" => snapshot.methods_with_sig_excluding_rbis,
132
+ "without signature" => snapshot.methods_without_sig_excluding_rbis,
133
+ }
134
+ print_map(methods_excluding_rbis_map, methods_excluding_rbis)
135
+ printn
107
136
  printl("Calls:")
108
137
  calls_map = {
109
138
  "typed" => snapshot.calls_typed,
@@ -119,6 +148,7 @@ module Spoom
119
148
  indent
120
149
  hash.each do |key, value|
121
150
  next unless value > 0
151
+
122
152
  printl("#{key}: #{value}#{percent(value, total)}")
123
153
  end
124
154
  dedent
@@ -127,6 +157,7 @@ module Spoom
127
157
  sig { params(value: T.nilable(Integer), total: T.nilable(Integer)).returns(String) }
128
158
  def percent(value, total)
129
159
  return "" if value.nil? || total.nil? || total == 0
160
+
130
161
  " (#{(value.to_f * 100.0 / total.to_f).round}%)"
131
162
  end
132
163
  end
@@ -9,94 +9,117 @@ require "date"
9
9
 
10
10
  module Spoom
11
11
  module Coverage
12
- extend T::Sig
13
-
14
- sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
15
- def self.snapshot(path: '.', rbi: true, sorbet_bin: nil)
16
- config = sorbet_config(path: path)
17
- config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
18
-
19
- new_config = config.copy
20
- new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" }
21
-
22
- metrics = Spoom::Sorbet.srb_metrics(
23
- "--no-config",
24
- "--no-error-sections",
25
- "--no-error-count",
26
- "--isolate-error-code=0",
27
- new_config.options_string,
28
- path: path,
29
- capture_err: true,
30
- sorbet_bin: sorbet_bin
31
- )
32
-
33
- snapshot = Snapshot.new
34
- return snapshot unless metrics
35
-
36
- sha = Spoom::Git.last_commit(path: path)
37
- snapshot.commit_sha = sha
38
- snapshot.commit_timestamp = Spoom::Git.commit_timestamp(sha, path: path).to_i if sha
39
-
40
- snapshot.files = metrics.fetch("types.input.files", 0)
41
- snapshot.modules = metrics.fetch("types.input.modules.total", 0)
42
- snapshot.classes = metrics.fetch("types.input.classes.total", 0)
43
- snapshot.singleton_classes = metrics.fetch("types.input.singleton_classes.total", 0)
44
- snapshot.methods_with_sig = metrics.fetch("types.sig.count", 0)
45
- snapshot.methods_without_sig = metrics.fetch("types.input.methods.total", 0) - snapshot.methods_with_sig
46
- snapshot.calls_typed = metrics.fetch("types.input.sends.typed", 0)
47
- snapshot.calls_untyped = metrics.fetch("types.input.sends.total", 0) - snapshot.calls_typed
48
-
49
- snapshot.duration += metrics.fetch("run.utilization.system_time.us", 0)
50
- snapshot.duration += metrics.fetch("run.utilization.user_time.us", 0)
51
-
52
- Snapshot::STRICTNESSES.each do |strictness|
53
- next unless metrics.key?("types.input.files.sigil.#{strictness}")
54
- snapshot.sigils[strictness] = T.must(metrics["types.input.files.sigil.#{strictness}"])
12
+ class << self
13
+ extend T::Sig
14
+
15
+ sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
16
+ def snapshot(path: ".", rbi: true, sorbet_bin: nil)
17
+ config = sorbet_config(path: path)
18
+ config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
19
+
20
+ new_config = config.copy
21
+ new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" }
22
+ flags = [
23
+ "--no-config",
24
+ "--no-error-sections",
25
+ "--no-error-count",
26
+ "--isolate-error-code=0",
27
+ new_config.options_string,
28
+ ]
29
+
30
+ metrics = Spoom::Sorbet.srb_metrics(
31
+ *flags,
32
+ path: path,
33
+ capture_err: true,
34
+ sorbet_bin: sorbet_bin,
35
+ )
36
+ # Collect extra information using a different configuration
37
+ flags << "--ignore sorbet/rbi/"
38
+ metrics_without_rbis = Spoom::Sorbet.srb_metrics(
39
+ *flags,
40
+ path: path,
41
+ capture_err: true,
42
+ sorbet_bin: sorbet_bin,
43
+ )
44
+
45
+ snapshot = Snapshot.new
46
+ return snapshot unless metrics
47
+
48
+ last_commit = Spoom::Git.last_commit(path: path)
49
+ snapshot.commit_sha = last_commit&.sha
50
+ snapshot.commit_timestamp = last_commit&.timestamp
51
+
52
+ snapshot.files = metrics.fetch("types.input.files", 0)
53
+ snapshot.modules = metrics.fetch("types.input.modules.total", 0)
54
+ snapshot.classes = metrics.fetch("types.input.classes.total", 0)
55
+ snapshot.singleton_classes = metrics.fetch("types.input.singleton_classes.total", 0)
56
+ snapshot.methods_with_sig = metrics.fetch("types.sig.count", 0)
57
+ snapshot.methods_without_sig = metrics.fetch("types.input.methods.total", 0) - snapshot.methods_with_sig
58
+ snapshot.calls_typed = metrics.fetch("types.input.sends.typed", 0)
59
+ snapshot.calls_untyped = metrics.fetch("types.input.sends.total", 0) - snapshot.calls_typed
60
+
61
+ snapshot.duration += metrics.fetch("run.utilization.system_time.us", 0)
62
+ snapshot.duration += metrics.fetch("run.utilization.user_time.us", 0)
63
+
64
+ if metrics_without_rbis
65
+ snapshot.methods_with_sig_excluding_rbis = metrics_without_rbis.fetch("types.sig.count", 0)
66
+ snapshot.methods_without_sig_excluding_rbis = metrics_without_rbis.fetch("types.input.methods.total",
67
+ 0) - snapshot.methods_with_sig_excluding_rbis
68
+ end
69
+
70
+ Snapshot::STRICTNESSES.each do |strictness|
71
+ if metrics.key?("types.input.files.sigil.#{strictness}")
72
+ snapshot.sigils[strictness] = T.must(metrics["types.input.files.sigil.#{strictness}"])
73
+ end
74
+ if metrics_without_rbis&.key?("types.input.files.sigil.#{strictness}")
75
+ snapshot.sigils_excluding_rbis[strictness] =
76
+ T.must(metrics_without_rbis["types.input.files.sigil.#{strictness}"])
77
+ end
78
+ end
79
+
80
+ snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
81
+ snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
82
+
83
+ files = Spoom::Sorbet.srb_files(new_config, path: path)
84
+ snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
85
+
86
+ snapshot
55
87
  end
56
88
 
57
- snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
58
- snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
59
-
60
- files = Spoom::Sorbet.srb_files(new_config, path: path)
61
- snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
62
-
63
- snapshot
64
- end
65
-
66
- sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) }
67
- def self.report(snapshots, palette:, path: ".")
68
- intro_commit = Git.sorbet_intro_commit(path: path)
69
- intro_date = intro_commit ? Git.commit_time(intro_commit, path: path) : nil
70
-
71
- Report.new(
72
- project_name: File.basename(File.expand_path(path)),
73
- palette: palette,
74
- snapshots: snapshots,
75
- sigils_tree: sigils_tree(path: path),
76
- sorbet_intro_commit: intro_commit,
77
- sorbet_intro_date: intro_date,
78
- )
79
- end
89
+ sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) }
90
+ def report(snapshots, palette:, path: ".")
91
+ intro_commit = Git.sorbet_intro_commit(path: path)
92
+
93
+ Report.new(
94
+ project_name: File.basename(File.expand_path(path)),
95
+ palette: palette,
96
+ snapshots: snapshots,
97
+ sigils_tree: sigils_tree(path: path),
98
+ sorbet_intro_commit: intro_commit&.sha,
99
+ sorbet_intro_date: intro_commit&.time,
100
+ )
101
+ end
80
102
 
81
- sig { params(path: String).returns(Sorbet::Config) }
82
- def self.sorbet_config(path: ".")
83
- Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
84
- end
103
+ sig { params(path: String).returns(Sorbet::Config) }
104
+ def sorbet_config(path: ".")
105
+ Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
106
+ end
85
107
 
86
- sig { params(path: String).returns(FileTree) }
87
- def self.sigils_tree(path: ".")
88
- config = sorbet_config(path: path)
89
- files = Sorbet.srb_files(config, path: path)
108
+ sig { params(path: String).returns(FileTree) }
109
+ def sigils_tree(path: ".")
110
+ config = sorbet_config(path: path)
111
+ files = Sorbet.srb_files(config, path: path)
90
112
 
91
- extensions = config.allowed_extensions
92
- extensions = [".rb"] if extensions.empty?
93
- extensions -= [".rbi"]
113
+ extensions = config.allowed_extensions
114
+ extensions = [".rb"] if extensions.empty?
115
+ extensions -= [".rbi"]
94
116
 
95
- pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
96
- files.select! { |file| file =~ pattern }
97
- files.reject! { |file| file =~ %r{/test/} }
117
+ pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
118
+ files.select! { |file| file =~ pattern }
119
+ files.reject! { |file| file =~ %r{/test/} }
98
120
 
99
- FileTree.new(files, strip_prefix: path)
121
+ FileTree.new(files, strip_prefix: path)
122
+ end
100
123
  end
101
124
  end
102
125
  end
@@ -33,6 +33,7 @@ module Spoom
33
33
  if path.empty? || parts.size == 1
34
34
  return @roots[path] ||= Node.new(parent: nil, name: path)
35
35
  end
36
+
36
37
  parent_path = T.must(parts[0...-1]).join("/")
37
38
  parent = add_path(parent_path)
38
39
  name = T.must(parts.last)
@@ -64,7 +65,7 @@ module Spoom
64
65
  out: T.any(IO, StringIO),
65
66
  show_strictness: T::Boolean,
66
67
  colors: T::Boolean,
67
- indent_level: Integer
68
+ indent_level: Integer,
68
69
  ).void
69
70
  end
70
71
  def print(out: $stdout, show_strictness: true, colors: true, indent_level: 0)
@@ -73,7 +74,7 @@ module Spoom
73
74
  out: out,
74
75
  show_strictness: show_strictness,
75
76
  colors: colors,
76
- indent_level: indent_level
77
+ indent_level: indent_level,
77
78
  )
78
79
  printer.print_tree
79
80
  end
@@ -105,6 +106,7 @@ module Spoom
105
106
  def path
106
107
  parent = self.parent
107
108
  return name unless parent
109
+
108
110
  "#{parent.path}/#{name}"
109
111
  end
110
112
  end
@@ -124,7 +126,7 @@ module Spoom
124
126
  out: T.any(IO, StringIO),
125
127
  show_strictness: T::Boolean,
126
128
  colors: T::Boolean,
127
- indent_level: Integer
129
+ indent_level: Integer,
128
130
  ).void
129
131
  end
130
132
  def initialize(tree:, out: $stdout, show_strictness: true, colors: true, indent_level: 0)