spoom 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 323ebe199d0efed57d5e92af12eb377c3c5af35e468c83db851b27f86b44117b
4
- data.tar.gz: c0fb24e2af1af3c0e460bb7c06ba9f3cff9cda471ec829ba00742b40dd67ab84
3
+ metadata.gz: b116c81229f1eb09a9e8676643fd54bb3436f7fa2534b06275d657bcd995f0c4
4
+ data.tar.gz: 93431ed795ccdcb7aa3afcec9cd3bc468c5b1893bfac4f269c33df9f52e32055
5
5
  SHA512:
6
- metadata.gz: faa5f86d378dd4cf56aa6e680cdee5717c59069b464b823bebfc63e7c8a7a40aeb7a75d86ec4dd0c69e025c4ad397eff740fdfa2ac134f49868736197836790a
7
- data.tar.gz: 67f7d4bc72282ff4bea575e85c2eed80f7195f94d3b518e3a84af57085bed90c757aedab5cfe33fb6d14dc96b62f21c6e8aa8cb56b79d4e9b148318a7c713e98
6
+ metadata.gz: 826328b46d178109253dc01197f1cf80102c2174d7dc5b3ac1d52c142fee9785e7100e4b444c4bba71e5303fa711d15f8368cd173177b3614b54ce14c60c5b09
7
+ data.tar.gz: 60a28d2220e2507d9deaf12d7e3f51e5ea08418f79685c40aa469da5bc317dbddfded2c8d49cd0102035e5a990d9fa60693ae65a7d1e7538118768bea1140f7f
@@ -54,6 +54,7 @@ module Spoom
54
54
  dry = options[:dry]
55
55
  only = options[:only]
56
56
  cmd = options[:suggest_bump_command]
57
+ directory = File.expand_path(directory)
57
58
  exec_path = File.expand_path(self.exec_path)
58
59
 
59
60
  unless Sorbet::Sigils.valid_strictness?(from)
@@ -73,11 +74,9 @@ module Spoom
73
74
 
74
75
  say("Checking files...")
75
76
 
76
- directory = File.expand_path(directory)
77
- files_to_bump = Sorbet::Sigils.files_with_sigil_strictness(directory, from)
78
-
79
- files_from_config = context.srb_files.map { |file| File.expand_path(file) }
80
- files_to_bump.select! { |file| files_from_config.include?(file) }
77
+ files_to_bump = context.srb_files_with_strictness(from, include_rbis: false)
78
+ .map { |file| File.expand_path(file, context.absolute_path) }
79
+ .select { |file| file.start_with?(directory) }
81
80
 
82
81
  if only
83
82
  list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
data/lib/spoom/cli.rb CHANGED
@@ -39,23 +39,19 @@ module Spoom
39
39
 
40
40
  desc "files", "List all the files typechecked by Sorbet"
41
41
  option :tree, type: :boolean, default: true, desc: "Display list as an indented tree"
42
- option :rbi, type: :boolean, default: true, desc: "Show RBI files"
42
+ option :rbi, type: :boolean, default: false, desc: "Show RBI files"
43
43
  def files
44
44
  context = context_requiring_sorbet!
45
- files = context.srb_files
46
-
47
- unless options[:rbi]
48
- files = files.reject { |file| file.end_with?(".rbi") }
49
- end
50
45
 
46
+ files = context.srb_files(include_rbis: options[:rbi])
51
47
  if files.empty?
52
48
  say_error("No file matching `#{Sorbet::CONFIG_PATH}`")
53
49
  exit(1)
54
50
  end
55
51
 
56
52
  if options[:tree]
57
- tree = FileTree.new(files, strip_prefix: exec_path)
58
- tree.print(colors: options[:color], indent_level: 0)
53
+ tree = FileTree.new(files)
54
+ tree.print_with_strictnesses(context, colors: options[:color])
59
55
  else
60
56
  puts files
61
57
  end
@@ -61,14 +61,32 @@ module Spoom
61
61
  end
62
62
 
63
63
  # List all files typechecked by Sorbet from its `config`
64
- sig { params(with_config: T.nilable(Spoom::Sorbet::Config)).returns(T::Array[String]) }
65
- def srb_files(with_config: nil)
64
+ sig { params(with_config: T.nilable(Spoom::Sorbet::Config), include_rbis: T::Boolean).returns(T::Array[String]) }
65
+ def srb_files(with_config: nil, include_rbis: true)
66
66
  config = with_config || sorbet_config
67
- regs = config.ignore.map { |string| Regexp.new(Regexp.escape(string)) }
68
- exts = config.allowed_extensions.empty? ? [".rb", ".rbi"] : config.allowed_extensions
69
- glob("**/*{#{exts.join(",")}}").reject do |f|
70
- regs.any? { |re| re.match?(f) }
71
- end.sort
67
+
68
+ allowed_extensions = config.allowed_extensions
69
+ allowed_extensions = Spoom::Sorbet::Config::DEFAULT_ALLOWED_EXTENSIONS if allowed_extensions.empty?
70
+ allowed_extensions -= [".rbi"] unless include_rbis
71
+
72
+ excluded_patterns = config.ignore.map { |string| File.join("**", string, "**") }
73
+
74
+ collector = FileCollector.new(allow_extensions: allowed_extensions, exclude_patterns: excluded_patterns)
75
+ collector.visit_paths(config.paths.map { |path| absolute_path_to(path) })
76
+ collector.files.map { |file| file.delete_prefix("#{absolute_path}/") }.sort
77
+ end
78
+
79
+ # List all files typechecked by Sorbet from its `config` that matches `strictness`
80
+ sig do
81
+ params(
82
+ strictness: String,
83
+ with_config: T.nilable(Spoom::Sorbet::Config),
84
+ include_rbis: T::Boolean,
85
+ ).returns(T::Array[String])
86
+ end
87
+ def srb_files_with_strictness(strictness, with_config: nil, include_rbis: true)
88
+ srb_files(with_config: with_config, include_rbis: include_rbis)
89
+ .select { |file| read_file_strictness(file) == strictness }
72
90
  end
73
91
 
74
92
  sig { params(arg: String, sorbet_bin: T.nilable(String), capture_err: T::Boolean).returns(T.nilable(String)) }
@@ -148,48 +148,34 @@ module Spoom
148
148
  class Sigils < CircleMap
149
149
  extend T::Sig
150
150
 
151
- sig { params(id: String, sigils_tree: FileTree).void }
152
- def initialize(id, sigils_tree)
153
- @scores = T.let({}, T::Hash[FileTree::Node, Float])
154
- @strictnesses = T.let({}, T::Hash[FileTree::Node, T.nilable(String)])
155
- @sigils_tree = sigils_tree
156
- super(id, sigils_tree.roots.map { |r| tree_node_to_json(r) })
151
+ sig do
152
+ params(
153
+ id: String,
154
+ file_tree: FileTree,
155
+ nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
156
+ nodes_scores: T::Hash[FileTree::Node, Float],
157
+ ).void
158
+ end
159
+ def initialize(id, file_tree, nodes_strictnesses, nodes_scores)
160
+ @nodes_strictnesses = nodes_strictnesses
161
+ @nodes_scores = nodes_scores
162
+ super(id, file_tree.roots.map { |r| tree_node_to_json(r) })
157
163
  end
158
164
 
159
165
  sig { params(node: FileTree::Node).returns(T::Hash[Symbol, T.untyped]) }
160
166
  def tree_node_to_json(node)
161
167
  if node.children.empty?
162
- return { name: node.name, strictness: tree_node_strictness(node) }
163
- end
164
-
165
- {
166
- name: node.name,
167
- children: node.children.values.map { |n| tree_node_to_json(n) },
168
- score: tree_node_score(node),
169
- }
170
- end
171
-
172
- sig { params(node: FileTree::Node).returns(T.nilable(String)) }
173
- def tree_node_strictness(node)
174
- prefix = @sigils_tree.strip_prefix
175
- path = node.path
176
- path = "#{prefix}/#{path}" if prefix
177
- @strictnesses[node] ||= Spoom::Sorbet::Sigils.file_strictness(path)
178
- end
179
-
180
- sig { params(node: FileTree::Node).returns(Float) }
181
- def tree_node_score(node)
182
- unless @scores.key?(node)
183
- if node.name =~ /\.rbi?$/
184
- case tree_node_strictness(node)
185
- when "true", "strict", "strong"
186
- @scores[node] = 1.0
187
- end
188
- elsif !node.children.empty?
189
- @scores[node] = node.children.values.sum { |n| tree_node_score(n) } / node.children.size.to_f
190
- end
168
+ {
169
+ name: node.name,
170
+ strictness: @nodes_strictnesses.fetch(node, "false"),
171
+ }
172
+ else
173
+ {
174
+ name: node.name,
175
+ children: node.children.values.map { |n| tree_node_to_json(n) },
176
+ score: @nodes_scores.fetch(node, 0.0),
177
+ }
191
178
  end
192
- @scores[node] || 0.0
193
179
  end
194
180
  end
195
181
  end
@@ -153,9 +153,24 @@ module Spoom
153
153
  class Map < Card
154
154
  extend T::Sig
155
155
 
156
- sig { params(sigils_tree: FileTree, title: String).void }
157
- def initialize(sigils_tree:, title: "Strictness Map")
158
- super(title: title, body: D3::CircleMap::Sigils.new("map_sigils", sigils_tree).html)
156
+ sig do
157
+ params(
158
+ file_tree: FileTree,
159
+ nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
160
+ nodes_strictness_scores: T::Hash[FileTree::Node, Float],
161
+ title: String,
162
+ ).void
163
+ end
164
+ def initialize(file_tree:, nodes_strictnesses:, nodes_strictness_scores:, title: "Strictness Map")
165
+ super(
166
+ title: title,
167
+ body: D3::CircleMap::Sigils.new(
168
+ "map_sigils",
169
+ file_tree,
170
+ nodes_strictnesses,
171
+ nodes_strictness_scores,
172
+ ).html
173
+ )
159
174
  end
160
175
  end
161
176
 
@@ -246,27 +261,14 @@ module Spoom
246
261
  class Report < Page
247
262
  extend T::Sig
248
263
 
249
- sig { returns(String) }
250
- attr_reader :project_name
251
-
252
- sig { returns(T.nilable(String)) }
253
- attr_reader :sorbet_intro_commit
254
-
255
- sig { returns(T.nilable(Time)) }
256
- attr_reader :sorbet_intro_date
257
-
258
- sig { returns(T::Array[Snapshot]) }
259
- attr_reader :snapshots
260
-
261
- sig { returns(FileTree) }
262
- attr_reader :sigils_tree
263
-
264
264
  sig do
265
265
  params(
266
266
  project_name: String,
267
267
  palette: D3::ColorPalette,
268
268
  snapshots: T::Array[Snapshot],
269
- sigils_tree: FileTree,
269
+ file_tree: FileTree,
270
+ nodes_strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
271
+ nodes_strictness_scores: T::Hash[FileTree::Node, Float],
270
272
  sorbet_intro_commit: T.nilable(String),
271
273
  sorbet_intro_date: T.nilable(Time),
272
274
  ).void
@@ -275,24 +277,28 @@ module Spoom
275
277
  project_name:,
276
278
  palette:,
277
279
  snapshots:,
278
- sigils_tree:,
280
+ file_tree:,
281
+ nodes_strictnesses:,
282
+ nodes_strictness_scores:,
279
283
  sorbet_intro_commit: nil,
280
284
  sorbet_intro_date: nil
281
285
  )
282
286
  super(title: project_name, palette: palette)
283
287
  @project_name = project_name
284
288
  @snapshots = snapshots
285
- @sigils_tree = sigils_tree
289
+ @file_tree = file_tree
290
+ @nodes_strictnesses = nodes_strictnesses
291
+ @nodes_strictness_scores = nodes_strictness_scores
286
292
  @sorbet_intro_commit = sorbet_intro_commit
287
293
  @sorbet_intro_date = sorbet_intro_date
288
294
  end
289
295
 
290
296
  sig { override.returns(String) }
291
297
  def header_html
292
- last = T.must(snapshots.last)
298
+ last = T.must(@snapshots.last)
293
299
  <<~ERB
294
300
  <h1 class="display-3">
295
- #{project_name}
301
+ #{@project_name}
296
302
  <span class="badge badge-pill badge-dark" style="font-size: 20%;">#{last.commit_sha}</span>
297
303
  </h1>
298
304
  ERB
@@ -300,17 +306,24 @@ module Spoom
300
306
 
301
307
  sig { override.returns(T::Array[Cards::Card]) }
302
308
  def cards
303
- last = T.must(snapshots.last)
309
+ last = T.must(@snapshots.last)
304
310
  cards = []
305
311
  cards << Cards::Snapshot.new(snapshot: last)
306
- cards << Cards::Map.new(sigils_tree: sigils_tree)
307
- cards << Cards::Timeline::Sigils.new(snapshots: snapshots)
308
- cards << Cards::Timeline::Calls.new(snapshots: snapshots)
309
- cards << Cards::Timeline::Sigs.new(snapshots: snapshots)
310
- cards << Cards::Timeline::RBIs.new(snapshots: snapshots)
311
- cards << Cards::Timeline::Versions.new(snapshots: snapshots)
312
- cards << Cards::Timeline::Runtimes.new(snapshots: snapshots)
313
- cards << Cards::SorbetIntro.new(sorbet_intro_commit: sorbet_intro_commit, sorbet_intro_date: sorbet_intro_date)
312
+ cards << Cards::Map.new(
313
+ file_tree: @file_tree,
314
+ nodes_strictnesses: @nodes_strictnesses,
315
+ nodes_strictness_scores: @nodes_strictness_scores,
316
+ )
317
+ cards << Cards::Timeline::Sigils.new(snapshots: @snapshots)
318
+ cards << Cards::Timeline::Calls.new(snapshots: @snapshots)
319
+ cards << Cards::Timeline::Sigs.new(snapshots: @snapshots)
320
+ cards << Cards::Timeline::RBIs.new(snapshots: @snapshots)
321
+ cards << Cards::Timeline::Versions.new(snapshots: @snapshots)
322
+ cards << Cards::Timeline::Runtimes.new(snapshots: @snapshots)
323
+ cards << Cards::SorbetIntro.new(
324
+ sorbet_intro_commit: @sorbet_intro_commit,
325
+ sorbet_intro_date: @sorbet_intro_date,
326
+ )
314
327
  cards
315
328
  end
316
329
  end
@@ -83,29 +83,29 @@ module Spoom
83
83
  def report(context, snapshots, palette:)
84
84
  intro_commit = context.sorbet_intro_commit
85
85
 
86
+ file_tree = file_tree(context)
87
+ v = FileTree::CollectScores.new(context)
88
+ v.visit_tree(file_tree)
89
+
86
90
  Report.new(
87
91
  project_name: File.basename(context.absolute_path),
88
92
  palette: palette,
89
93
  snapshots: snapshots,
90
- sigils_tree: sigils_tree(context),
94
+ file_tree: file_tree,
95
+ nodes_strictnesses: v.strictnesses,
96
+ nodes_strictness_scores: v.scores,
91
97
  sorbet_intro_commit: intro_commit&.sha,
92
98
  sorbet_intro_date: intro_commit&.time,
93
99
  )
94
100
  end
95
101
 
96
102
  sig { params(context: Context).returns(FileTree) }
97
- def sigils_tree(context)
98
- files = context.srb_files
99
-
100
- extensions = context.sorbet_config.allowed_extensions
101
- extensions = [".rb"] if extensions.empty?
102
- extensions -= [".rbi"]
103
-
104
- pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
105
- files.select! { |file| file =~ pattern }
106
- files.reject! { |file| file =~ %r{/test/} }
103
+ def file_tree(context)
104
+ config = context.sorbet_config
105
+ config.ignore += ["test"]
107
106
 
108
- FileTree.new(files, strip_prefix: context.absolute_path)
107
+ files = context.srb_files(with_config: config, include_rbis: false)
108
+ FileTree.new(files)
109
109
  end
110
110
  end
111
111
  end
@@ -0,0 +1,79 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ class FileCollector
6
+ extend T::Sig
7
+
8
+ sig { returns(T::Array[String]) }
9
+ attr_reader :files
10
+
11
+ # Initialize a new file collector
12
+ #
13
+ # If `allow_extensions` is empty, all files are collected.
14
+ # If `allow_extensions` is an array of extensions, only files with one of these extensions are collected.
15
+ sig do
16
+ params(
17
+ allow_extensions: T::Array[String],
18
+ exclude_patterns: T::Array[String],
19
+ ).void
20
+ end
21
+ def initialize(allow_extensions: [], exclude_patterns: [])
22
+ @files = T.let([], T::Array[String])
23
+ @allow_extensions = allow_extensions
24
+ @exclude_patterns = exclude_patterns
25
+ end
26
+
27
+ sig { params(paths: T::Array[String]).void }
28
+ def visit_paths(paths)
29
+ paths.each { |path| visit_path(path) }
30
+ end
31
+
32
+ sig { params(path: String).void }
33
+ def visit_path(path)
34
+ path = clean_path(path)
35
+
36
+ return if excluded_path?(path)
37
+
38
+ if File.file?(path)
39
+ visit_file(path)
40
+ elsif File.directory?(path)
41
+ visit_directory(path)
42
+ else # rubocop:disable Style/EmptyElse
43
+ # Ignore aliases, sockets, etc.
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ sig { params(path: String).returns(String) }
50
+ def clean_path(path)
51
+ Pathname.new(path).cleanpath.to_s
52
+ end
53
+
54
+ sig { params(path: String).void }
55
+ def visit_file(path)
56
+ return if excluded_file?(path)
57
+
58
+ @files << path
59
+ end
60
+
61
+ sig { params(path: String).void }
62
+ def visit_directory(path)
63
+ visit_paths(Dir.glob("#{path}/*"))
64
+ end
65
+
66
+ sig { params(path: String).returns(T::Boolean) }
67
+ def excluded_file?(path)
68
+ return false if @allow_extensions.empty?
69
+
70
+ extension = File.extname(path)
71
+ @allow_extensions.none? { |allowed| extension == allowed }
72
+ end
73
+
74
+ sig { params(path: String).returns(T::Boolean) }
75
+ def excluded_path?(path)
76
+ @exclude_patterns.any? { |pattern| File.fnmatch?(pattern, path) }
77
+ end
78
+ end
79
+ end
@@ -6,13 +6,9 @@ module Spoom
6
6
  class FileTree
7
7
  extend T::Sig
8
8
 
9
- sig { returns(T.nilable(String)) }
10
- attr_reader :strip_prefix
11
-
12
- sig { params(paths: T::Enumerable[String], strip_prefix: T.nilable(String)).void }
13
- def initialize(paths = [], strip_prefix: nil)
9
+ sig { params(paths: T::Enumerable[String]).void }
10
+ def initialize(paths = [])
14
11
  @roots = T.let({}, T::Hash[String, Node])
15
- @strip_prefix = strip_prefix
16
12
  add_paths(paths)
17
13
  end
18
14
 
@@ -27,8 +23,6 @@ module Spoom
27
23
  # This will create all nodes until the root of `path`.
28
24
  sig { params(path: String).returns(Node) }
29
25
  def add_path(path)
30
- prefix = @strip_prefix
31
- path = path.delete_prefix("#{prefix}/") if prefix
32
26
  parts = path.split("/")
33
27
  if path.empty? || parts.size == 1
34
28
  return @roots[path] ||= Node.new(parent: nil, name: path)
@@ -49,43 +43,51 @@ module Spoom
49
43
  # All the nodes in this tree
50
44
  sig { returns(T::Array[Node]) }
51
45
  def nodes
52
- all_nodes = []
53
- @roots.values.each { |root| collect_nodes(root, all_nodes) }
54
- all_nodes
46
+ v = CollectNodes.new
47
+ v.visit_tree(self)
48
+ v.nodes
55
49
  end
56
50
 
57
51
  # All the paths in this tree
58
52
  sig { returns(T::Array[String]) }
59
53
  def paths
60
- nodes.collect(&:path)
54
+ nodes.map(&:path)
61
55
  end
62
56
 
63
- sig do
64
- params(
65
- out: T.any(IO, StringIO),
66
- show_strictness: T::Boolean,
67
- colors: T::Boolean,
68
- indent_level: Integer,
69
- ).void
57
+ # Return a map of strictnesses for each node in the tree
58
+ sig { params(context: Context).returns(T::Hash[Node, T.nilable(String)]) }
59
+ def nodes_strictnesses(context)
60
+ v = CollectStrictnesses.new(context)
61
+ v.visit_tree(self)
62
+ v.strictnesses
70
63
  end
71
- def print(out: $stdout, show_strictness: true, colors: true, indent_level: 0)
72
- printer = TreePrinter.new(
73
- tree: self,
74
- out: out,
75
- show_strictness: show_strictness,
76
- colors: colors,
77
- indent_level: indent_level,
78
- )
79
- printer.print_tree
64
+
65
+ # Return a map of typing scores for each node in the tree
66
+ sig { params(context: Context).returns(T::Hash[Node, Float]) }
67
+ def nodes_strictness_scores(context)
68
+ v = CollectScores.new(context)
69
+ v.visit_tree(self)
70
+ v.scores
80
71
  end
81
72
 
82
- private
73
+ # Return a map of typing scores for each path in the tree
74
+ sig { params(context: Context).returns(T::Hash[String, Float]) }
75
+ def paths_strictness_scores(context)
76
+ nodes_strictness_scores(context).map { |node, score| [node.path, score] }.to_h
77
+ end
83
78
 
84
- sig { params(node: FileTree::Node, collected_nodes: T::Array[Node]).returns(T::Array[Node]) }
85
- def collect_nodes(node, collected_nodes = [])
86
- collected_nodes << node
87
- node.children.values.each { |child| collect_nodes(child, collected_nodes) }
88
- collected_nodes
79
+ sig { params(out: T.any(IO, StringIO), colors: T::Boolean).void }
80
+ def print(out: $stdout, colors: true)
81
+ printer = Printer.new({}, out: out, colors: colors)
82
+ printer.visit_tree(self)
83
+ end
84
+
85
+ sig { params(context: Context, out: T.any(IO, StringIO), colors: T::Boolean).void }
86
+ def print_with_strictnesses(context, out: $stdout, colors: true)
87
+ strictnesses = nodes_strictnesses(context)
88
+
89
+ printer = Printer.new(strictnesses, out: out, colors: colors)
90
+ printer.visit_tree(self)
89
91
  end
90
92
 
91
93
  # A node representing either a file or a directory inside a FileTree
@@ -111,77 +113,160 @@ module Spoom
111
113
  end
112
114
  end
113
115
 
116
+ # An abstract visitor for FileTree
117
+ class Visitor
118
+ extend T::Sig
119
+ extend T::Helpers
120
+
121
+ abstract!
122
+
123
+ sig { params(tree: FileTree).void }
124
+ def visit_tree(tree)
125
+ visit_nodes(tree.roots)
126
+ end
127
+
128
+ sig { params(node: FileTree::Node).void }
129
+ def visit_node(node)
130
+ visit_nodes(node.children.values)
131
+ end
132
+
133
+ sig { params(nodes: T::Array[FileTree::Node]).void }
134
+ def visit_nodes(nodes)
135
+ nodes.each { |node| visit_node(node) }
136
+ end
137
+ end
138
+
139
+ # A visitor that collects all the nodes in a tree
140
+ class CollectNodes < Visitor
141
+ extend T::Sig
142
+
143
+ sig { returns(T::Array[FileTree::Node]) }
144
+ attr_reader :nodes
145
+
146
+ sig { void }
147
+ def initialize
148
+ super()
149
+ @nodes = T.let([], T::Array[FileTree::Node])
150
+ end
151
+
152
+ sig { override.params(node: FileTree::Node).void }
153
+ def visit_node(node)
154
+ @nodes << node
155
+ super
156
+ end
157
+ end
158
+
159
+ # A visitor that collects the strictness of each node in a tree
160
+ class CollectStrictnesses < Visitor
161
+ extend T::Sig
162
+
163
+ sig { returns(T::Hash[Node, T.nilable(String)]) }
164
+ attr_reader :strictnesses
165
+
166
+ sig { params(context: Context).void }
167
+ def initialize(context)
168
+ super()
169
+ @context = context
170
+ @strictnesses = T.let({}, T::Hash[Node, T.nilable(String)])
171
+ end
172
+
173
+ sig { override.params(node: FileTree::Node).void }
174
+ def visit_node(node)
175
+ path = node.path
176
+ @strictnesses[node] = @context.read_file_strictness(path) if @context.file?(path)
177
+
178
+ super
179
+ end
180
+ end
181
+
182
+ # A visitor that collects the typing score of each node in a tree
183
+ class CollectScores < CollectStrictnesses
184
+ extend T::Sig
185
+
186
+ sig { returns(T::Hash[Node, Float]) }
187
+ attr_reader :scores
188
+
189
+ sig { params(context: Context).void }
190
+ def initialize(context)
191
+ super
192
+ @context = context
193
+ @scores = T.let({}, T::Hash[Node, Float])
194
+ end
195
+
196
+ sig { override.params(node: FileTree::Node).void }
197
+ def visit_node(node)
198
+ super
199
+
200
+ @scores[node] = node_score(node)
201
+ end
202
+
203
+ private
204
+
205
+ sig { params(node: Node).returns(Float) }
206
+ def node_score(node)
207
+ if @context.file?(node.path)
208
+ strictness_score(@strictnesses[node])
209
+ else
210
+ node.children.values.sum { |child| @scores.fetch(child, 0.0) } / node.children.size.to_f
211
+ end
212
+ end
213
+
214
+ sig { params(strictness: T.nilable(String)).returns(Float) }
215
+ def strictness_score(strictness)
216
+ case strictness
217
+ when "true", "strict", "strong"
218
+ 1.0
219
+ else
220
+ 0.0
221
+ end
222
+ end
223
+ end
224
+
114
225
  # An internal class used to print a FileTree
115
226
  #
116
227
  # See `FileTree#print`
117
- class TreePrinter < Spoom::Printer
228
+ class Printer < Visitor
118
229
  extend T::Sig
119
230
 
120
- sig { returns(FileTree) }
121
- attr_reader :tree
122
-
123
231
  sig do
124
232
  params(
125
- tree: FileTree,
233
+ strictnesses: T::Hash[FileTree::Node, T.nilable(String)],
126
234
  out: T.any(IO, StringIO),
127
- show_strictness: T::Boolean,
128
235
  colors: T::Boolean,
129
- indent_level: Integer,
130
236
  ).void
131
237
  end
132
- def initialize(tree:, out: $stdout, show_strictness: true, colors: true, indent_level: 0)
133
- super(out: out, colors: colors, indent_level: indent_level)
134
- @tree = tree
135
- @show_strictness = show_strictness
238
+ def initialize(strictnesses, out: $stdout, colors: true)
239
+ super()
240
+ @strictnesses = strictnesses
241
+ @colors = colors
242
+ @printer = T.let(Spoom::Printer.new(out: out, colors: colors), Spoom::Printer)
136
243
  end
137
244
 
138
- sig { void }
139
- def print_tree
140
- print_nodes(tree.roots)
141
- end
142
-
143
- sig { params(node: FileTree::Node).void }
144
- def print_node(node)
145
- printt
245
+ sig { override.params(node: FileTree::Node).void }
246
+ def visit_node(node)
247
+ @printer.printt
146
248
  if node.children.empty?
147
- if @show_strictness
148
- strictness = node_strictness(node)
149
- if @colors
150
- print_colored(node.name, strictness_color(strictness))
151
- elsif strictness
152
- print("#{node.name} (#{strictness})")
153
- else
154
- print(node.name.to_s)
155
- end
249
+ strictness = @strictnesses[node]
250
+ if @colors
251
+ @printer.print_colored(node.name, strictness_color(strictness))
252
+ elsif strictness
253
+ @printer.print("#{node.name} (#{strictness})")
156
254
  else
157
- print(node.name.to_s)
255
+ @printer.print(node.name.to_s)
158
256
  end
159
- print("\n")
257
+ @printer.print("\n")
160
258
  else
161
- print_colored(node.name, Color::BLUE)
162
- print("/")
163
- printn
164
- indent
165
- print_nodes(node.children.values)
166
- dedent
259
+ @printer.print_colored(node.name, Color::BLUE)
260
+ @printer.print("/")
261
+ @printer.printn
262
+ @printer.indent
263
+ super
264
+ @printer.dedent
167
265
  end
168
266
  end
169
267
 
170
- sig { params(nodes: T::Array[FileTree::Node]).void }
171
- def print_nodes(nodes)
172
- nodes.each { |node| print_node(node) }
173
- end
174
-
175
268
  private
176
269
 
177
- sig { params(node: FileTree::Node).returns(T.nilable(String)) }
178
- def node_strictness(node)
179
- path = node.path
180
- prefix = tree.strip_prefix
181
- path = "#{prefix}/#{path}" if prefix
182
- Spoom::Sorbet::Sigils.file_strictness(path)
183
- end
184
-
185
270
  sig { params(strictness: T.nilable(String)).returns(Color) }
186
271
  def strictness_color(strictness)
187
272
  case strictness
@@ -26,8 +26,10 @@ module Spoom
26
26
  class Config
27
27
  extend T::Sig
28
28
 
29
+ DEFAULT_ALLOWED_EXTENSIONS = T.let([".rb", ".rbi"].freeze, T::Array[String])
30
+
29
31
  sig { returns(T::Array[String]) }
30
- attr_reader :paths, :ignore, :allowed_extensions
32
+ attr_accessor :paths, :ignore, :allowed_extensions
31
33
 
32
34
  sig { returns(T::Boolean) }
33
35
  attr_accessor :no_stdlib
@@ -85,21 +85,6 @@ module Spoom
85
85
  change_sigil_in_file(path, new_strictness)
86
86
  end
87
87
  end
88
-
89
- # finds all files in the specified directory with the passed strictness
90
- sig do
91
- params(
92
- directory: T.any(String, Pathname),
93
- strictness: String,
94
- extension: String,
95
- ).returns(T::Array[String])
96
- end
97
- def files_with_sigil_strictness(directory, strictness, extension: ".rb")
98
- paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
99
- paths.filter do |path|
100
- file_strictness(path) == strictness
101
- end
102
- end
103
88
  end
104
89
  end
105
90
  end
data/lib/spoom/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Spoom
5
- VERSION = "1.2.0"
5
+ VERSION = "1.2.1"
6
6
  end
data/lib/spoom.rb CHANGED
@@ -12,6 +12,7 @@ module Spoom
12
12
  class Error < StandardError; end
13
13
  end
14
14
 
15
+ require "spoom/file_collector"
15
16
  require "spoom/context"
16
17
  require "spoom/colors"
17
18
  require "spoom/sorbet"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spoom
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Terrasa
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-30 00:00:00.000000000 Z
11
+ date: 2023-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -143,6 +143,7 @@ files:
143
143
  - lib/spoom/coverage/d3/timeline.rb
144
144
  - lib/spoom/coverage/report.rb
145
145
  - lib/spoom/coverage/snapshot.rb
146
+ - lib/spoom/file_collector.rb
146
147
  - lib/spoom/file_tree.rb
147
148
  - lib/spoom/printer.rb
148
149
  - lib/spoom/sorbet.rb