spoom 1.1.11 → 1.1.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)