spoom 1.6.3 → 1.7.1
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/lib/spoom/cli/srb/assertions.rb +1 -1
- data/lib/spoom/cli/srb/metrics.rb +68 -0
- data/lib/spoom/cli/srb/sigs.rb +8 -10
- data/lib/spoom/cli/srb.rb +4 -0
- data/lib/spoom/context/sorbet.rb +1 -1
- data/lib/spoom/counters.rb +22 -0
- data/lib/spoom/deadcode/index.rb +2 -2
- data/lib/spoom/deadcode/plugins/active_record.rb +19 -0
- data/lib/spoom/model/builder.rb +10 -15
- data/lib/spoom/model/model.rb +1 -1
- data/lib/spoom/parse.rb +4 -18
- data/lib/spoom/rbs.rb +77 -0
- data/lib/spoom/sorbet/metrics/code_metrics_visitor.rb +236 -0
- data/lib/spoom/sorbet/metrics/metrics_file_parser.rb +34 -0
- data/lib/spoom/sorbet/metrics.rb +2 -30
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +239 -0
- data/lib/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments.rb +123 -0
- data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +293 -0
- data/lib/spoom/sorbet/translate/strip_sorbet_sigs.rb +23 -0
- data/lib/spoom/sorbet/translate/translator.rb +71 -0
- data/lib/spoom/sorbet/translate.rb +49 -0
- data/lib/spoom/sorbet.rb +1 -1
- data/lib/spoom/source/rewriter.rb +167 -0
- data/lib/spoom/source.rb +4 -0
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +3 -0
- data/rbi/spoom.rbi +337 -178
- metadata +29 -4
- data/lib/spoom/sorbet/assertions.rb +0 -278
- data/lib/spoom/sorbet/sigs.rb +0 -281
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87a613b9da2addba3344ec53859a248cfbdc6805f7e24a87797b92f0487a1697
|
4
|
+
data.tar.gz: 61b5686efef0f8bc03ff948ef5053330b067318d1535ffbdcdc84b0b29ccc80a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e70eef5d6ccd57d8ae2eb766493cf16ba26eee6ed5e210c0340e87c11e7c7f80881cdd3e1513bff1fe7a46417972709f1f65988ebd254a624cffc004d7f8459a
|
7
|
+
data.tar.gz: ecf60bcaebaf6cd10498762858a47690ec0550b8259659fcef5af635090cf82120826fb8e15163610ed0497bc62920175815325715b893460910af4766b74fe0
|
@@ -19,7 +19,7 @@ module Spoom
|
|
19
19
|
"in `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n")
|
20
20
|
|
21
21
|
transformed_files = transform_files(files) do |file, contents|
|
22
|
-
Spoom::Sorbet::
|
22
|
+
Spoom::Sorbet::Translate.sorbet_assertions_to_rbs_comments(contents, file: file)
|
23
23
|
end
|
24
24
|
|
25
25
|
say("Translated type assertions in `#{transformed_files}` file#{transformed_files == 1 ? "" : "s"}.")
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Cli
|
6
|
+
module Srb
|
7
|
+
class Metrics < Thor
|
8
|
+
include Helper
|
9
|
+
|
10
|
+
default_task :show
|
11
|
+
|
12
|
+
desc "show", "Show metrics about Sorbet usage"
|
13
|
+
option :dump, type: :boolean, default: false
|
14
|
+
def show(*paths)
|
15
|
+
files = collect_files(paths)
|
16
|
+
metrics = Spoom::Sorbet::Metrics.collect_code_metrics(files)
|
17
|
+
|
18
|
+
if options[:dump]
|
19
|
+
metrics.sort_by { |key, _value| key }.each do |key, value|
|
20
|
+
puts "#{key} #{value}"
|
21
|
+
end
|
22
|
+
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
say("Files: `#{files.size}`")
|
27
|
+
|
28
|
+
["classes", "modules", "singleton_classes"].each do |key|
|
29
|
+
value = metrics[key]
|
30
|
+
next if value == 0
|
31
|
+
|
32
|
+
say("\n#{key.capitalize}: `#{value}`")
|
33
|
+
["with_srb_type_params", "with_rbs_type_params"].each do |subkey|
|
34
|
+
say(" * #{subkey.gsub("_", " ")}: `#{metrics["#{key}_#{subkey}"]}`")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
["methods", "accessors"].each do |key|
|
39
|
+
value = metrics[key]
|
40
|
+
next if value == 0
|
41
|
+
|
42
|
+
say("\n#{key.capitalize}: `#{value}`")
|
43
|
+
["without_sig", "with_srb_sig", "with_rbs_sig"].each do |subkey|
|
44
|
+
say(" * #{subkey.gsub("_", " ")}: `#{metrics["#{key}_#{subkey}"]}`")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
say("\nT. calls: `#{metrics["T_calls"]}`")
|
49
|
+
metrics
|
50
|
+
.select { |key, _value| key.start_with?("T.") }
|
51
|
+
.sort_by { |_key, value| -value }
|
52
|
+
.each do |key, value|
|
53
|
+
say(" * #{key}: `#{value}`")
|
54
|
+
end
|
55
|
+
|
56
|
+
say("\nRBS Assertions: `#{metrics["rbs_assertions"]}`")
|
57
|
+
metrics
|
58
|
+
.reject { |key, _value| key == "rbs_assertions" }
|
59
|
+
.select { |key, _value| key.start_with?("rbs_") }
|
60
|
+
.sort_by { |_key, value| -value }
|
61
|
+
.each do |key, value|
|
62
|
+
say(" * #{key}: `#{value}`")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/spoom/cli/srb/sigs.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "spoom/sorbet/sigs"
|
5
|
-
|
6
4
|
module Spoom
|
7
5
|
module Cli
|
8
6
|
module Srb
|
@@ -34,12 +32,12 @@ module Spoom
|
|
34
32
|
|
35
33
|
case from
|
36
34
|
when "rbi"
|
37
|
-
transformed_files = transform_files(files) do |
|
38
|
-
Spoom::Sorbet::
|
35
|
+
transformed_files = transform_files(files) do |file, contents|
|
36
|
+
Spoom::Sorbet::Translate.sorbet_sigs_to_rbs_comments(contents, file: file, positional_names: options[:positional_names])
|
39
37
|
end
|
40
38
|
when "rbs"
|
41
|
-
transformed_files = transform_files(files) do |
|
42
|
-
Spoom::Sorbet::
|
39
|
+
transformed_files = transform_files(files) do |file, contents|
|
40
|
+
Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs(contents, file: file)
|
43
41
|
end
|
44
42
|
end
|
45
43
|
|
@@ -52,8 +50,8 @@ module Spoom
|
|
52
50
|
|
53
51
|
say("Stripping signatures from `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n")
|
54
52
|
|
55
|
-
transformed_files = transform_files(files) do |
|
56
|
-
Spoom::Sorbet::
|
53
|
+
transformed_files = transform_files(files) do |file, contents|
|
54
|
+
Spoom::Sorbet::Translate.strip_sorbet_sigs(contents, file: file)
|
57
55
|
end
|
58
56
|
|
59
57
|
say("Stripped signatures from `#{transformed_files}` file#{transformed_files == 1 ? "" : "s"}.")
|
@@ -93,8 +91,8 @@ module Spoom
|
|
93
91
|
# Then, we transform the copied files to translate all the RBS signatures into RBI signatures.
|
94
92
|
say("Translating signatures from RBS to RBI...")
|
95
93
|
files = collect_files([copy_context.absolute_path])
|
96
|
-
transform_files(files) do |
|
97
|
-
Spoom::Sorbet::
|
94
|
+
transform_files(files) do |file, contents|
|
95
|
+
Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs(contents, file: file)
|
98
96
|
end
|
99
97
|
|
100
98
|
# We need to inject `extend T::Sig` to be sure all the classes can run the `sig{}` blocks.
|
data/lib/spoom/cli/srb.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative "srb/assertions"
|
|
5
5
|
require_relative "srb/bump"
|
6
6
|
require_relative "srb/coverage"
|
7
7
|
require_relative "srb/lsp"
|
8
|
+
require_relative "srb/metrics"
|
8
9
|
require_relative "srb/sigs"
|
9
10
|
require_relative "srb/tc"
|
10
11
|
|
@@ -24,6 +25,9 @@ module Spoom
|
|
24
25
|
desc "lsp", "Send LSP requests to Sorbet"
|
25
26
|
subcommand "lsp", Spoom::Cli::Srb::LSP
|
26
27
|
|
28
|
+
desc "metrics", "Collect metrics about Sorbet usage"
|
29
|
+
subcommand "metrics", Spoom::Cli::Srb::Metrics
|
30
|
+
|
27
31
|
desc "sigs", "Translate signatures from/to RBI and RBS"
|
28
32
|
subcommand "sigs", Spoom::Cli::Srb::Sigs
|
29
33
|
|
data/lib/spoom/context/sorbet.rb
CHANGED
@@ -48,7 +48,7 @@ module Spoom
|
|
48
48
|
return unless file?(metrics_file)
|
49
49
|
|
50
50
|
metrics_path = absolute_path_to(metrics_file)
|
51
|
-
metrics = Spoom::Sorbet::
|
51
|
+
metrics = Spoom::Sorbet::Metrics::MetricsFileParser.parse_file(metrics_path)
|
52
52
|
remove!(metrics_file)
|
53
53
|
metrics
|
54
54
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
#: [K = String, V = Integer, Elem = [String, Integer]]
|
6
|
+
class Counters < Hash
|
7
|
+
#: -> void
|
8
|
+
def initialize
|
9
|
+
super(0)
|
10
|
+
end
|
11
|
+
|
12
|
+
#: (String) -> void
|
13
|
+
def increment(key)
|
14
|
+
self[key] += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
#: (String) -> Integer
|
18
|
+
def [](key)
|
19
|
+
super(key) #: as Integer
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/spoom/deadcode/index.rb
CHANGED
@@ -49,10 +49,10 @@ module Spoom
|
|
49
49
|
|
50
50
|
#: (String rb, file: String, ?plugins: Array[Plugins::Base]) -> void
|
51
51
|
def index_ruby(rb, file:, plugins: [])
|
52
|
-
node
|
52
|
+
node = Spoom.parse_ruby(rb, file: file, comments: true)
|
53
53
|
|
54
54
|
# Index definitions
|
55
|
-
model_builder = Model::Builder.new(@model, file
|
55
|
+
model_builder = Model::Builder.new(@model, file)
|
56
56
|
model_builder.visit(node)
|
57
57
|
|
58
58
|
# Index references
|
@@ -41,6 +41,11 @@ module Spoom
|
|
41
41
|
"before_validation",
|
42
42
|
].freeze #: Array[String]
|
43
43
|
|
44
|
+
CALLBACK_CONDITIONS = [
|
45
|
+
"if",
|
46
|
+
"unless",
|
47
|
+
].freeze #: Array[String]
|
48
|
+
|
44
49
|
CRUD_METHODS = [
|
45
50
|
"assign_attributes",
|
46
51
|
"create",
|
@@ -63,9 +68,23 @@ module Spoom
|
|
63
68
|
#: (Send send) -> void
|
64
69
|
def on_send(send)
|
65
70
|
if send.recv.nil? && CALLBACKS.include?(send.name)
|
71
|
+
# Process direct symbol arguments
|
66
72
|
send.each_arg(Prism::SymbolNode) do |arg|
|
67
73
|
@index.reference_method(arg.unescaped, send.location)
|
68
74
|
end
|
75
|
+
|
76
|
+
# Process hash arguments for conditions like if: :method_name
|
77
|
+
send.each_arg_assoc do |key, value|
|
78
|
+
key = key.slice.delete_suffix(":")
|
79
|
+
|
80
|
+
case key
|
81
|
+
when *CALLBACK_CONDITIONS
|
82
|
+
if value&.is_a?(Prism::SymbolNode)
|
83
|
+
@index.reference_method(value.unescaped, send.location)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
69
88
|
return
|
70
89
|
end
|
71
90
|
|
data/lib/spoom/model/builder.rb
CHANGED
@@ -5,15 +5,12 @@ module Spoom
|
|
5
5
|
class Model
|
6
6
|
# Populate a Model by visiting the nodes from a Ruby file
|
7
7
|
class Builder < NamespaceVisitor
|
8
|
-
#: (Model model, String file
|
9
|
-
def initialize(model, file
|
8
|
+
#: (Model model, String file) -> void
|
9
|
+
def initialize(model, file)
|
10
10
|
super()
|
11
11
|
|
12
12
|
@model = model
|
13
13
|
@file = file
|
14
|
-
@comments_by_line = comments.to_h do |c|
|
15
|
-
[c.location.start_line, c]
|
16
|
-
end #: Hash[Integer, Prism::Comment]
|
17
14
|
@namespace_nesting = [] #: Array[Namespace]
|
18
15
|
@visibility_stack = [Visibility::Public] #: Array[Visibility]
|
19
16
|
@last_sigs = [] #: Array[Sig]
|
@@ -263,22 +260,20 @@ module Spoom
|
|
263
260
|
|
264
261
|
#: (Prism::Node node) -> Array[Comment]
|
265
262
|
def node_comments(node)
|
263
|
+
last_line = node.location.start_line
|
266
264
|
comments = []
|
267
265
|
|
268
|
-
|
269
|
-
|
266
|
+
node.location.leading_comments.reverse_each do |comment|
|
267
|
+
if comment.location.start_line < last_line - 1
|
268
|
+
break
|
269
|
+
end
|
270
270
|
|
271
|
-
|
272
|
-
comment = @comments_by_line[line]
|
273
|
-
break unless comment
|
271
|
+
last_line = comment.location.start_line
|
274
272
|
|
275
|
-
|
273
|
+
comments.unshift(Comment.new(
|
276
274
|
comment.slice.gsub(/^#\s?/, "").rstrip,
|
277
275
|
Location.from_prism(@file, comment.location),
|
278
|
-
)
|
279
|
-
|
280
|
-
comments.unshift(spoom_comment)
|
281
|
-
@comments_by_line.delete(line)
|
276
|
+
))
|
282
277
|
end
|
283
278
|
|
284
279
|
comments
|
data/lib/spoom/model/model.rb
CHANGED
@@ -84,7 +84,7 @@ module Spoom
|
|
84
84
|
#: Array[Comment]
|
85
85
|
attr_reader :comments
|
86
86
|
|
87
|
-
#: (Symbol symbol, owner: Namespace?, location: Location,
|
87
|
+
#: (Symbol symbol, owner: Namespace?, location: Location, comments: Array[Comment]) -> void
|
88
88
|
def initialize(symbol, owner:, location:, comments:)
|
89
89
|
@symbol = symbol
|
90
90
|
@owner = owner
|
data/lib/spoom/parse.rb
CHANGED
@@ -7,8 +7,8 @@ module Spoom
|
|
7
7
|
class ParseError < Error; end
|
8
8
|
|
9
9
|
class << self
|
10
|
-
#: (String ruby, file: String) -> Prism::Node
|
11
|
-
def parse_ruby(ruby, file:)
|
10
|
+
#: (String ruby, file: String, ?comments: bool) -> Prism::Node
|
11
|
+
def parse_ruby(ruby, file:, comments: false)
|
12
12
|
result = Prism.parse(ruby)
|
13
13
|
unless result.success?
|
14
14
|
message = +"Error while parsing #{file}:\n"
|
@@ -20,23 +20,9 @@ module Spoom
|
|
20
20
|
raise ParseError, message
|
21
21
|
end
|
22
22
|
|
23
|
-
result.
|
24
|
-
end
|
25
|
-
|
26
|
-
#: (String ruby, file: String) -> [Prism::Node, Array[Prism::Comment]]
|
27
|
-
def parse_ruby_with_comments(ruby, file:)
|
28
|
-
result = Prism.parse(ruby)
|
29
|
-
unless result.success?
|
30
|
-
message = +"Error while parsing #{file}:\n"
|
31
|
-
|
32
|
-
result.errors.each do |e|
|
33
|
-
message << "- #{e.message} (at #{e.location.start_line}:#{e.location.start_column})\n"
|
34
|
-
end
|
23
|
+
result.attach_comments! if comments
|
35
24
|
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
[result.value, result.comments]
|
25
|
+
result.value
|
40
26
|
end
|
41
27
|
end
|
42
28
|
end
|
data/lib/spoom/rbs.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module RBS
|
6
|
+
class Comments
|
7
|
+
#: Array[Annotations]
|
8
|
+
attr_reader :annotations
|
9
|
+
|
10
|
+
#: Array[Signature]
|
11
|
+
attr_reader :signatures
|
12
|
+
|
13
|
+
#: -> void
|
14
|
+
def initialize
|
15
|
+
@annotations = [] #: Array[Annotations]
|
16
|
+
@signatures = [] #: Array[Signature]
|
17
|
+
end
|
18
|
+
|
19
|
+
#: -> bool
|
20
|
+
def empty?
|
21
|
+
@annotations.empty? && @signatures.empty?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Comment
|
26
|
+
#: String
|
27
|
+
attr_reader :string
|
28
|
+
|
29
|
+
#: Prism::Location
|
30
|
+
attr_reader :location
|
31
|
+
|
32
|
+
#: (String, Prism::Location) -> void
|
33
|
+
def initialize(string, location)
|
34
|
+
@string = string
|
35
|
+
@location = location
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Annotations < Comment; end
|
40
|
+
class Signature < Comment; end
|
41
|
+
|
42
|
+
module ExtractRBSComments
|
43
|
+
#: (Prism::Node) -> Comments
|
44
|
+
def node_rbs_comments(node)
|
45
|
+
res = Comments.new
|
46
|
+
|
47
|
+
comments = node.location.leading_comments.reverse
|
48
|
+
return res if comments.empty?
|
49
|
+
|
50
|
+
continuation_comments = [] #: Array[Prism::Comment]
|
51
|
+
|
52
|
+
comments.each do |comment|
|
53
|
+
string = comment.slice
|
54
|
+
|
55
|
+
if string.start_with?("# @")
|
56
|
+
string = string.delete_prefix("#").strip
|
57
|
+
res.annotations << Annotations.new(string, comment.location)
|
58
|
+
elsif string.start_with?("#: ")
|
59
|
+
string = string.delete_prefix("#:").strip
|
60
|
+
location = comment.location
|
61
|
+
|
62
|
+
continuation_comments.reverse_each do |continuation_comment|
|
63
|
+
string = "#{string}#{continuation_comment.slice.delete_prefix("#|")}"
|
64
|
+
location = location.join(continuation_comment.location)
|
65
|
+
end
|
66
|
+
continuation_comments.clear
|
67
|
+
res.signatures << Signature.new(string, location)
|
68
|
+
elsif string.start_with?("#|")
|
69
|
+
continuation_comments << comment
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
res
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module Sorbet
|
6
|
+
module Metrics
|
7
|
+
class << self
|
8
|
+
#: (Array[String]) -> Spoom::Counters
|
9
|
+
def collect_code_metrics(files)
|
10
|
+
counters = Counters.new
|
11
|
+
|
12
|
+
files.each do |file|
|
13
|
+
counters.increment("files")
|
14
|
+
|
15
|
+
content = File.read(file)
|
16
|
+
node = Spoom.parse_ruby(content, file: file, comments: true)
|
17
|
+
visitor = CodeMetricsVisitor.new(counters)
|
18
|
+
visitor.visit(node)
|
19
|
+
end
|
20
|
+
|
21
|
+
counters
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Collects metrics about how Sorbet is used in the codebase.
|
26
|
+
#
|
27
|
+
# This approach is different from the metrics file we get directly from Sorbet.
|
28
|
+
#
|
29
|
+
# This visitor actually visits the codebase and collects metrics about the amount of signatures, `T.` calls,
|
30
|
+
# and other metrics. It also knows about RBS comments.
|
31
|
+
#
|
32
|
+
# On the other hand, the metrics file is a snapshot of the metrics at type checking time and knows about
|
33
|
+
# is calls are typed, how many assertions are done, etc.
|
34
|
+
class CodeMetricsVisitor < Spoom::Visitor
|
35
|
+
include RBS::ExtractRBSComments
|
36
|
+
|
37
|
+
#: Counters
|
38
|
+
attr_reader :counters
|
39
|
+
|
40
|
+
#: (Spoom::Counters) -> void
|
41
|
+
def initialize(counters)
|
42
|
+
super()
|
43
|
+
|
44
|
+
@counters = counters
|
45
|
+
|
46
|
+
@last_sigs = [] #: Array[Prism::CallNode]
|
47
|
+
@type_params = [] #: Array[Prism::CallNode]
|
48
|
+
end
|
49
|
+
|
50
|
+
# @override
|
51
|
+
#: (Prism::Node?) -> void
|
52
|
+
def visit(node)
|
53
|
+
return if node.nil?
|
54
|
+
|
55
|
+
node.location.trailing_comments.each do |comment|
|
56
|
+
text = comment.slice.strip
|
57
|
+
next unless text.start_with?("#:")
|
58
|
+
|
59
|
+
@counters.increment("rbs_assertions")
|
60
|
+
|
61
|
+
case text
|
62
|
+
when /^#: as !nil/
|
63
|
+
@counters.increment("rbs_must")
|
64
|
+
when /^#: as untyped/
|
65
|
+
@counters.increment("rbs_unsafe")
|
66
|
+
when /^#: as/
|
67
|
+
@counters.increment("rbs_cast")
|
68
|
+
when /^#:/
|
69
|
+
@counters.increment("rbs_let")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
# @override
|
77
|
+
#: (Prism::ClassNode) -> void
|
78
|
+
def visit_class_node(node)
|
79
|
+
visit_scope(node) do
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @override
|
85
|
+
#: (Prism::ModuleNode) -> void
|
86
|
+
def visit_module_node(node)
|
87
|
+
visit_scope(node) do
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @override
|
93
|
+
#: (Prism::SingletonClassNode) -> void
|
94
|
+
def visit_singleton_class_node(node)
|
95
|
+
visit_scope(node) do
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# @override
|
101
|
+
#: (Prism::DefNode) -> void
|
102
|
+
def visit_def_node(node)
|
103
|
+
unless node.name.to_s.start_with?("test_")
|
104
|
+
@counters.increment("methods")
|
105
|
+
|
106
|
+
rbs_sigs = node_rbs_comments(node).signatures
|
107
|
+
srb_sigs = collect_last_srb_sigs
|
108
|
+
|
109
|
+
if rbs_sigs.any?
|
110
|
+
@counters.increment("methods_with_rbs_sig")
|
111
|
+
end
|
112
|
+
|
113
|
+
if srb_sigs.any?
|
114
|
+
@counters.increment("methods_with_srb_sig")
|
115
|
+
end
|
116
|
+
|
117
|
+
if rbs_sigs.empty? && srb_sigs.empty?
|
118
|
+
@counters.increment("methods_without_sig")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
super
|
123
|
+
end
|
124
|
+
|
125
|
+
# @override
|
126
|
+
#: (Prism::CallNode) -> void
|
127
|
+
def visit_call_node(node)
|
128
|
+
@counters.increment("calls")
|
129
|
+
|
130
|
+
case node.name
|
131
|
+
when :attr_accessor, :attr_reader, :attr_writer
|
132
|
+
visit_attr_accessor(node)
|
133
|
+
return
|
134
|
+
when :sig
|
135
|
+
visit_sig(node)
|
136
|
+
return
|
137
|
+
when :type_member, :type_template
|
138
|
+
visit_type_member(node)
|
139
|
+
return
|
140
|
+
end
|
141
|
+
|
142
|
+
case node.receiver&.slice
|
143
|
+
when /^(::)?T$/
|
144
|
+
@counters.increment("T_calls")
|
145
|
+
@counters.increment("T.#{node.name}")
|
146
|
+
end
|
147
|
+
|
148
|
+
super
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
#: (Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode) { -> void } -> void
|
154
|
+
def visit_scope(node, &block)
|
155
|
+
key = node_key(node)
|
156
|
+
@counters.increment(key)
|
157
|
+
@counters.increment("#{key}_with_rbs_type_params") if node_rbs_comments(node).signatures.any?
|
158
|
+
|
159
|
+
old_type_params = @type_params
|
160
|
+
@type_params = []
|
161
|
+
|
162
|
+
yield
|
163
|
+
|
164
|
+
@counters.increment("#{key}_with_srb_type_params") if @type_params.any?
|
165
|
+
|
166
|
+
@type_params = old_type_params
|
167
|
+
end
|
168
|
+
|
169
|
+
#: (Prism::CallNode) -> void
|
170
|
+
def visit_attr_accessor(node)
|
171
|
+
@counters.increment("accessors")
|
172
|
+
|
173
|
+
rbs_sigs = node_rbs_comments(node).signatures
|
174
|
+
srb_sigs = collect_last_srb_sigs
|
175
|
+
|
176
|
+
if rbs_sigs.any?
|
177
|
+
@counters.increment("accessors_with_rbs_sig")
|
178
|
+
end
|
179
|
+
|
180
|
+
if srb_sigs.any?
|
181
|
+
@counters.increment("accessors_with_srb_sig")
|
182
|
+
end
|
183
|
+
|
184
|
+
if rbs_sigs.empty? && srb_sigs.empty?
|
185
|
+
@counters.increment("accessors_without_sig")
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
#: (Prism::CallNode) -> void
|
190
|
+
def visit_sig(node)
|
191
|
+
@last_sigs << node
|
192
|
+
@counters.increment("srb_sigs")
|
193
|
+
|
194
|
+
if node.slice =~ /abstract/
|
195
|
+
@counters.increment("srb_sigs_abstract")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
#: (Prism::CallNode) -> void
|
200
|
+
def visit_type_member(node)
|
201
|
+
key = case node.name
|
202
|
+
when :type_member
|
203
|
+
"type_members"
|
204
|
+
when :type_template
|
205
|
+
"type_templates"
|
206
|
+
else
|
207
|
+
return
|
208
|
+
end
|
209
|
+
|
210
|
+
@counters.increment(key)
|
211
|
+
|
212
|
+
@type_params << node
|
213
|
+
end
|
214
|
+
|
215
|
+
#: -> Array[Prism::CallNode]
|
216
|
+
def collect_last_srb_sigs
|
217
|
+
sigs = @last_sigs.dup
|
218
|
+
@last_sigs.clear
|
219
|
+
sigs
|
220
|
+
end
|
221
|
+
|
222
|
+
#: (Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode) -> String
|
223
|
+
def node_key(node)
|
224
|
+
case node
|
225
|
+
when Prism::ClassNode
|
226
|
+
"classes"
|
227
|
+
when Prism::ModuleNode
|
228
|
+
"modules"
|
229
|
+
when Prism::SingletonClassNode
|
230
|
+
"singleton_classes"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "spoom/sorbet/sigils"
|
5
|
+
|
6
|
+
module Spoom
|
7
|
+
module Sorbet
|
8
|
+
module Metrics
|
9
|
+
module MetricsFileParser
|
10
|
+
DEFAULT_PREFIX = "ruby_typer.unknown."
|
11
|
+
|
12
|
+
class << self
|
13
|
+
#: (String path, ?String prefix) -> Hash[String, Integer]
|
14
|
+
def parse_file(path, prefix = DEFAULT_PREFIX)
|
15
|
+
parse_string(File.read(path), prefix)
|
16
|
+
end
|
17
|
+
|
18
|
+
#: (String string, ?String prefix) -> Hash[String, Integer]
|
19
|
+
def parse_string(string, prefix = DEFAULT_PREFIX)
|
20
|
+
parse_hash(JSON.parse(string), prefix)
|
21
|
+
end
|
22
|
+
|
23
|
+
#: (Hash[String, untyped] obj, ?String prefix) -> Counters
|
24
|
+
def parse_hash(obj, prefix = DEFAULT_PREFIX)
|
25
|
+
obj["metrics"].each_with_object(Counters.new) do |metric, metrics|
|
26
|
+
name = metric["name"].sub(prefix, "")
|
27
|
+
metrics[name] = metric["value"] || 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|