spoom 1.7.14 → 1.7.16
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/Gemfile +1 -0
- data/lib/spoom/cli/srb/metrics.rb +1 -1
- data/lib/spoom/cli/srb/sigs.rb +4 -2
- data/lib/spoom/context/bundle.rb +7 -2
- data/lib/spoom/context/sorbet.rb +7 -2
- data/lib/spoom/file_collector.rb +9 -3
- data/lib/spoom/rbs.rb +1 -1
- data/lib/spoom/sorbet/metrics/metrics_file_parser.rb +1 -0
- data/lib/spoom/sorbet/sigils.rb +7 -0
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +74 -4
- data/lib/spoom/sorbet/translate.rb +8 -3
- data/lib/spoom/version.rb +1 -1
- data/rbi/spoom.rbi +49 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2110b75c9f1cfaf75e50c901f26af66d149f2a6353a22ee2154062f24f808472
|
|
4
|
+
data.tar.gz: 1f8daac5e4e94b7b135ed0b2d7dafb701dcf338302f337982ced3e6a32aa65fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb98e214157092a61b830ce8e5836b1bc9c304d902b2886e10ab52c753febd6c7233142a509b461b26ee9c1c1150bd8346b02370c031bda68a6c555b2bc1c128
|
|
7
|
+
data.tar.gz: 6b3b0ec9bbc739161a4c642084aa1edcae82cf9293e20e3997656cfa2108eff1c32369c5d4225c6ebdd0716cb030eaec7c749cfa623273cdb59dce848f5ab039
|
data/Gemfile
CHANGED
data/lib/spoom/cli/srb/sigs.rb
CHANGED
|
@@ -213,8 +213,10 @@ module Spoom
|
|
|
213
213
|
contents = contents.force_encoding(encoding)
|
|
214
214
|
end
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
new_contents = block.call(file, contents)
|
|
217
|
+
next if new_contents == contents
|
|
218
|
+
|
|
219
|
+
File.write(file, new_contents)
|
|
218
220
|
transformed_count += 1
|
|
219
221
|
rescue RBI::Error => error
|
|
220
222
|
say_warning("Can't parse #{file}: #{error.message}")
|
data/lib/spoom/context/bundle.rb
CHANGED
|
@@ -10,12 +10,16 @@ module Spoom
|
|
|
10
10
|
#: -> String?
|
|
11
11
|
def read_gemfile
|
|
12
12
|
read("Gemfile")
|
|
13
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
14
|
+
nil
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
# Read the contents of the Gemfile.lock in this context directory
|
|
16
18
|
#: -> String?
|
|
17
19
|
def read_gemfile_lock
|
|
18
20
|
read("Gemfile.lock")
|
|
21
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
22
|
+
nil
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
# Set the `contents` of the Gemfile in this context directory
|
|
@@ -45,9 +49,10 @@ module Spoom
|
|
|
45
49
|
|
|
46
50
|
#: -> Hash[String, Bundler::LazySpecification]
|
|
47
51
|
def gemfile_lock_specs
|
|
48
|
-
|
|
52
|
+
lockfile = read_gemfile_lock
|
|
53
|
+
return {} unless lockfile
|
|
49
54
|
|
|
50
|
-
parser = Bundler::LockfileParser.new(
|
|
55
|
+
parser = Bundler::LockfileParser.new(lockfile)
|
|
51
56
|
parser.specs.to_h { |spec| [spec.name, spec] }
|
|
52
57
|
end
|
|
53
58
|
|
data/lib/spoom/context/sorbet.rb
CHANGED
|
@@ -42,10 +42,15 @@ module Spoom
|
|
|
42
42
|
sorbet_bin: sorbet_bin,
|
|
43
43
|
capture_err: capture_err,
|
|
44
44
|
)
|
|
45
|
-
return unless file?(metrics_file)
|
|
46
45
|
|
|
47
46
|
metrics_path = absolute_path_to(metrics_file)
|
|
48
|
-
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
metrics = Spoom::Sorbet::Metrics::MetricsFileParser.parse_file(metrics_path)
|
|
50
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
49
54
|
remove!(metrics_file)
|
|
50
55
|
metrics
|
|
51
56
|
end
|
data/lib/spoom/file_collector.rb
CHANGED
|
@@ -33,11 +33,17 @@ module Spoom
|
|
|
33
33
|
|
|
34
34
|
return if excluded_path?(path)
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
begin
|
|
37
|
+
stat = File.stat(path)
|
|
38
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if stat.file?
|
|
37
43
|
visit_file(path)
|
|
38
|
-
elsif
|
|
44
|
+
elsif stat.directory?
|
|
39
45
|
visit_directory(path)
|
|
40
|
-
else
|
|
46
|
+
else
|
|
41
47
|
# Ignore aliases, sockets, etc.
|
|
42
48
|
end
|
|
43
49
|
end
|
data/lib/spoom/rbs.rb
CHANGED
|
@@ -105,7 +105,7 @@ module Spoom
|
|
|
105
105
|
location = location.join(continuation_comment.location)
|
|
106
106
|
end
|
|
107
107
|
continuation_comments.clear
|
|
108
|
-
res.signatures
|
|
108
|
+
res.signatures.prepend(Signature.new(string, location))
|
|
109
109
|
elsif string.start_with?("#|")
|
|
110
110
|
continuation_comments << comment
|
|
111
111
|
end
|
|
@@ -10,6 +10,7 @@ module Spoom
|
|
|
10
10
|
DEFAULT_PREFIX = "ruby_typer.unknown."
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
|
+
# Raises if `path` doesn't point to a valid file that we have access to (see `File.read` for details)
|
|
13
14
|
#: (String path, ?String prefix) -> Hash[String, Integer]
|
|
14
15
|
def parse_file(path, prefix = DEFAULT_PREFIX)
|
|
15
16
|
parse_string(File.read(path), prefix)
|
data/lib/spoom/sorbet/sigils.rb
CHANGED
|
@@ -44,6 +44,13 @@ module Spoom
|
|
|
44
44
|
SIGIL_REGEXP.match(content)&.[](1)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# returns true if the passed content contains a valid sigil
|
|
48
|
+
#: (String content) -> bool
|
|
49
|
+
def contains_valid_sigil?(content)
|
|
50
|
+
strictness = strictness_in_content(content)
|
|
51
|
+
!!strictness && valid_strictness?(strictness)
|
|
52
|
+
end
|
|
53
|
+
|
|
47
54
|
# returns a string which is the passed content but with the sigil updated to a new strictness
|
|
48
55
|
#: (String content, String new_strictness) -> String
|
|
49
56
|
def update_sigil(content, new_strictness)
|
|
@@ -7,11 +7,46 @@ module Spoom
|
|
|
7
7
|
class RBSCommentsToSorbetSigs < Translator
|
|
8
8
|
include Spoom::RBS::ExtractRBSComments
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
RBS_ANNOTATION_MARKERS = [
|
|
11
|
+
"# @abstract",
|
|
12
|
+
"# @interface",
|
|
13
|
+
"# @sealed",
|
|
14
|
+
"# @final",
|
|
15
|
+
"# @requires_ancestor:",
|
|
16
|
+
"# @override",
|
|
17
|
+
"# @overridable",
|
|
18
|
+
"# @without_runtime",
|
|
19
|
+
].freeze #: Array[String]
|
|
20
|
+
RBS_REWRITE_PATTERN = Regexp.union(["#:", "#|", *RBS_ANNOTATION_MARKERS]).freeze #: Regexp
|
|
21
|
+
private_constant :RBS_ANNOTATION_MARKERS, :RBS_REWRITE_PATTERN
|
|
22
|
+
|
|
23
|
+
ALLOWED_OVERLOAD_STRATEGIES = [:translate_all, :translate_last, :raise].freeze #: Array[Symbol]
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
#: (String source) -> bool
|
|
27
|
+
def contains_rbs_syntax?(source)
|
|
28
|
+
Sigils.contains_valid_sigil?(source) && source.match?(RBS_REWRITE_PATTERN)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#: (String ruby_contents, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> String
|
|
32
|
+
def rewrite_if_needed(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
|
|
33
|
+
return ruby_contents unless contains_rbs_syntax?(ruby_contents)
|
|
34
|
+
|
|
35
|
+
new(ruby_contents, file:, max_line_length:, overloads_strategy:).rewrite
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#: (String, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> void
|
|
40
|
+
def initialize(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
|
|
12
41
|
super(ruby_contents, file: file)
|
|
13
42
|
|
|
43
|
+
unless ALLOWED_OVERLOAD_STRATEGIES.include?(overloads_strategy)
|
|
44
|
+
raise ArgumentError, "Unknown overloads_strategy: #{overloads_strategy.inspect}. " \
|
|
45
|
+
"Must be one of: #{ALLOWED_OVERLOAD_STRATEGIES.map(&:inspect).join(", ")}"
|
|
46
|
+
end
|
|
47
|
+
|
|
14
48
|
@max_line_length = max_line_length
|
|
49
|
+
@overloads_strategy = overloads_strategy
|
|
15
50
|
end
|
|
16
51
|
|
|
17
52
|
# @override
|
|
@@ -80,7 +115,13 @@ module Spoom
|
|
|
80
115
|
|
|
81
116
|
return if comments.signatures.empty?
|
|
82
117
|
|
|
83
|
-
|
|
118
|
+
signatures = apply_overloads_strategy(
|
|
119
|
+
comments.signatures,
|
|
120
|
+
method_name: node.message.to_s,
|
|
121
|
+
location: "#{@file}:#{node.location.start_line}",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
signatures.each do |signature|
|
|
84
125
|
attr_type = ::RBS::Parser.parse_type(signature.string)
|
|
85
126
|
sig = RBI::Sig.new
|
|
86
127
|
|
|
@@ -116,11 +157,17 @@ module Spoom
|
|
|
116
157
|
return if comments.empty?
|
|
117
158
|
return if comments.signatures.empty?
|
|
118
159
|
|
|
160
|
+
signatures = apply_overloads_strategy(
|
|
161
|
+
comments.signatures,
|
|
162
|
+
method_name: def_node.name.to_s,
|
|
163
|
+
location: "#{@file}:#{def_node.location.start_line}",
|
|
164
|
+
)
|
|
165
|
+
|
|
119
166
|
builder = RBI::Parser::TreeBuilder.new(@ruby_contents, comments: [], file: @file)
|
|
120
167
|
builder.visit(def_node)
|
|
121
168
|
rbi_node = builder.tree.nodes.first #: as RBI::Method
|
|
122
169
|
|
|
123
|
-
|
|
170
|
+
signatures.each do |signature|
|
|
124
171
|
begin
|
|
125
172
|
method_type = ::RBS::Parser.parse_method_type(signature.string)
|
|
126
173
|
rescue ::RBS::ParsingError
|
|
@@ -153,6 +200,29 @@ module Spoom
|
|
|
153
200
|
end
|
|
154
201
|
end
|
|
155
202
|
|
|
203
|
+
#: (Array[RBS::Signature], method_name: String, location: String) -> Array[RBS::Signature]
|
|
204
|
+
def apply_overloads_strategy(signatures, method_name:, location:)
|
|
205
|
+
return signatures if signatures.size <= 1
|
|
206
|
+
|
|
207
|
+
case @overloads_strategy
|
|
208
|
+
when :translate_all
|
|
209
|
+
signatures
|
|
210
|
+
when :translate_last
|
|
211
|
+
kept = signatures.last #: as RBS::Signature
|
|
212
|
+
others = signatures[0...-1] #: as !nil
|
|
213
|
+
|
|
214
|
+
# Delete all the signatures we didn't keep
|
|
215
|
+
others.each do |signature|
|
|
216
|
+
from = adjust_to_line_start(signature.location.start_offset)
|
|
217
|
+
to = adjust_to_line_end(signature.location.end_offset)
|
|
218
|
+
@rewriter << Source::Delete.new(from, to)
|
|
219
|
+
end
|
|
220
|
+
[kept]
|
|
221
|
+
else # :raise
|
|
222
|
+
raise Error, "Method `#{method_name}` at #{location} has multiple overloaded signatures"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
156
226
|
#: (Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode) -> void
|
|
157
227
|
def apply_class_annotations(node)
|
|
158
228
|
comments = node_rbs_comments(node)
|
|
@@ -53,9 +53,14 @@ module Spoom
|
|
|
53
53
|
|
|
54
54
|
# Converts all the RBS comments in the given Ruby code to `sig` nodes.
|
|
55
55
|
# It also handles type members and class annotations.
|
|
56
|
-
#: (String ruby_contents, file: String, ?max_line_length: Integer?) -> String
|
|
57
|
-
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: nil)
|
|
58
|
-
RBSCommentsToSorbetSigs.
|
|
56
|
+
#: (String ruby_contents, file: String, ?max_line_length: Integer?, ?overloads_strategy: Symbol) -> String
|
|
57
|
+
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: nil, overloads_strategy: :translate_all)
|
|
58
|
+
RBSCommentsToSorbetSigs.rewrite_if_needed(
|
|
59
|
+
ruby_contents,
|
|
60
|
+
file: file,
|
|
61
|
+
max_line_length: max_line_length,
|
|
62
|
+
overloads_strategy: overloads_strategy,
|
|
63
|
+
)
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
# Converts all `T.let` and `T.cast` nodes to RBS comments in the given Ruby code.
|
data/lib/spoom/version.rb
CHANGED
data/rbi/spoom.rbi
CHANGED
|
@@ -2823,6 +2823,9 @@ module Spoom::Sorbet::Sigils
|
|
|
2823
2823
|
sig { params(path_list: T::Array[::String], new_strictness: ::String).returns(T::Array[::String]) }
|
|
2824
2824
|
def change_sigil_in_files(path_list, new_strictness); end
|
|
2825
2825
|
|
|
2826
|
+
sig { params(content: ::String).returns(T::Boolean) }
|
|
2827
|
+
def contains_valid_sigil?(content); end
|
|
2828
|
+
|
|
2826
2829
|
sig { params(path: T.any(::Pathname, ::String)).returns(T.nilable(::String)) }
|
|
2827
2830
|
def file_strictness(path); end
|
|
2828
2831
|
|
|
@@ -2851,8 +2854,15 @@ Spoom::Sorbet::Sigils::VALID_STRICTNESS = T.let(T.unsafe(nil), Array)
|
|
|
2851
2854
|
|
|
2852
2855
|
module Spoom::Sorbet::Translate
|
|
2853
2856
|
class << self
|
|
2854
|
-
sig
|
|
2855
|
-
|
|
2857
|
+
sig do
|
|
2858
|
+
params(
|
|
2859
|
+
ruby_contents: ::String,
|
|
2860
|
+
file: ::String,
|
|
2861
|
+
max_line_length: T.nilable(::Integer),
|
|
2862
|
+
overloads_strategy: ::Symbol
|
|
2863
|
+
).returns(::String)
|
|
2864
|
+
end
|
|
2865
|
+
def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: T.unsafe(nil), overloads_strategy: T.unsafe(nil)); end
|
|
2856
2866
|
|
|
2857
2867
|
sig do
|
|
2858
2868
|
params(
|
|
@@ -2890,8 +2900,15 @@ class Spoom::Sorbet::Translate::Error < ::Spoom::Error; end
|
|
|
2890
2900
|
class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs < ::Spoom::Sorbet::Translate::Translator
|
|
2891
2901
|
include ::Spoom::RBS::ExtractRBSComments
|
|
2892
2902
|
|
|
2893
|
-
sig
|
|
2894
|
-
|
|
2903
|
+
sig do
|
|
2904
|
+
params(
|
|
2905
|
+
ruby_contents: ::String,
|
|
2906
|
+
file: ::String,
|
|
2907
|
+
max_line_length: T.nilable(::Integer),
|
|
2908
|
+
overloads_strategy: ::Symbol
|
|
2909
|
+
).void
|
|
2910
|
+
end
|
|
2911
|
+
def initialize(ruby_contents, file:, max_line_length: T.unsafe(nil), overloads_strategy: T.unsafe(nil)); end
|
|
2895
2912
|
|
|
2896
2913
|
sig { override.params(node: ::Prism::CallNode).void }
|
|
2897
2914
|
def visit_call_node(node); end
|
|
@@ -2927,6 +2944,15 @@ class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs < ::Spoom::Sorbet::Trans
|
|
|
2927
2944
|
sig { params(annotations: T::Array[::Spoom::RBS::Annotation], sig: ::RBI::Sig).void }
|
|
2928
2945
|
def apply_member_annotations(annotations, sig); end
|
|
2929
2946
|
|
|
2947
|
+
sig do
|
|
2948
|
+
params(
|
|
2949
|
+
signatures: T::Array[::Spoom::RBS::Signature],
|
|
2950
|
+
method_name: ::String,
|
|
2951
|
+
location: ::String
|
|
2952
|
+
).returns(T::Array[::Spoom::RBS::Signature])
|
|
2953
|
+
end
|
|
2954
|
+
def apply_overloads_strategy(signatures, method_name:, location:); end
|
|
2955
|
+
|
|
2930
2956
|
sig { params(comments: T::Array[::Prism::Comment]).void }
|
|
2931
2957
|
def apply_type_aliases(comments); end
|
|
2932
2958
|
|
|
@@ -2938,8 +2964,27 @@ class Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs < ::Spoom::Sorbet::Trans
|
|
|
2938
2964
|
|
|
2939
2965
|
sig { params(node: ::Prism::CallNode).void }
|
|
2940
2966
|
def visit_attr(node); end
|
|
2967
|
+
|
|
2968
|
+
class << self
|
|
2969
|
+
sig { params(source: ::String).returns(T::Boolean) }
|
|
2970
|
+
def contains_rbs_syntax?(source); end
|
|
2971
|
+
|
|
2972
|
+
sig do
|
|
2973
|
+
params(
|
|
2974
|
+
ruby_contents: ::String,
|
|
2975
|
+
file: ::String,
|
|
2976
|
+
max_line_length: T.nilable(::Integer),
|
|
2977
|
+
overloads_strategy: ::Symbol
|
|
2978
|
+
).returns(::String)
|
|
2979
|
+
end
|
|
2980
|
+
def rewrite_if_needed(ruby_contents, file:, max_line_length: T.unsafe(nil), overloads_strategy: T.unsafe(nil)); end
|
|
2981
|
+
end
|
|
2941
2982
|
end
|
|
2942
2983
|
|
|
2984
|
+
Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::ALLOWED_OVERLOAD_STRATEGIES = T.let(T.unsafe(nil), Array)
|
|
2985
|
+
Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::RBS_ANNOTATION_MARKERS = T.let(T.unsafe(nil), Array)
|
|
2986
|
+
Spoom::Sorbet::Translate::RBSCommentsToSorbetSigs::RBS_REWRITE_PATTERN = T.let(T.unsafe(nil), Regexp)
|
|
2987
|
+
|
|
2943
2988
|
class Spoom::Sorbet::Translate::SorbetAssertionsToRBSComments < ::Spoom::Sorbet::Translate::Translator
|
|
2944
2989
|
sig do
|
|
2945
2990
|
params(
|