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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e36809d27f631413357c7b2287d7c51de3b8ed7d17ed7da03d4943bed071b23
4
- data.tar.gz: b299e3da2929b3a80b1466566e22b207ddceeab71f8ee98ccbc85d259f39fcf8
3
+ metadata.gz: 2110b75c9f1cfaf75e50c901f26af66d149f2a6353a22ee2154062f24f808472
4
+ data.tar.gz: 1f8daac5e4e94b7b135ed0b2d7dafb701dcf338302f337982ced3e6a32aa65fa
5
5
  SHA512:
6
- metadata.gz: c39b635f9e3b26ca6bfa42ae4d89758d83e7f5c06bc4b7e263dae5f6b3b3debd7845e6cf90fdf54b854b1a8019957bc662dc36a05f2e560f6bb7e65a0dc224ba
7
- data.tar.gz: f1ec06e214fc1f98ceb3915d659fe745951515e2edbd16219ebe1f77cb4d3ab79a28409b352dc35f92f9c9dbe85dcf176e696d7b1d48eae7bd665f21f12f997e
6
+ metadata.gz: bb98e214157092a61b830ce8e5836b1bc9c304d902b2886e10ab52c753febd6c7233142a509b461b26ee9c1c1150bd8346b02370c031bda68a6c555b2bc1c128
7
+ data.tar.gz: 6b3b0ec9bbc739161a4c642084aa1edcae82cf9293e20e3997656cfa2108eff1c32369c5d4225c6ebdd0716cb030eaec7c749cfa623273cdb59dce848f5ab039
data/Gemfile CHANGED
@@ -12,5 +12,6 @@ group :development do
12
12
  gem "debug"
13
13
  gem "rubocop-shopify", require: false
14
14
  gem "rubocop-sorbet", require: false
15
+ gem "rubocop-minitest", require: false
15
16
  gem "tapioca", require: false
16
17
  end
@@ -60,7 +60,7 @@ module Spoom
60
60
  .sort_by { |_key, value| -value }
61
61
  .each do |key, value|
62
62
  say(" * #{key}: `#{value}`")
63
- end
63
+ end
64
64
  end
65
65
  end
66
66
  end
@@ -213,8 +213,10 @@ module Spoom
213
213
  contents = contents.force_encoding(encoding)
214
214
  end
215
215
 
216
- contents = block.call(file, contents)
217
- File.write(file, contents)
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}")
@@ -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
- return {} unless file?("Gemfile.lock")
52
+ lockfile = read_gemfile_lock
53
+ return {} unless lockfile
49
54
 
50
- parser = Bundler::LockfileParser.new(read_gemfile_lock)
55
+ parser = Bundler::LockfileParser.new(lockfile)
51
56
  parser.specs.to_h { |spec| [spec.name, spec] }
52
57
  end
53
58
 
@@ -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
- metrics = Spoom::Sorbet::Metrics::MetricsFileParser.parse_file(metrics_path)
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
@@ -33,11 +33,17 @@ module Spoom
33
33
 
34
34
  return if excluded_path?(path)
35
35
 
36
- if File.file?(path)
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 File.directory?(path)
44
+ elsif stat.directory?
39
45
  visit_directory(path)
40
- else # rubocop:disable Style/EmptyElse
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 << Signature.new(string, location)
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)
@@ -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
- #: (String, file: String, ?max_line_length: Integer?) -> void
11
- def initialize(ruby_contents, file:, max_line_length: nil)
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
- comments.signatures.each do |signature|
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
- comments.signatures.each do |signature|
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.new(ruby_contents, file: file, max_line_length: max_line_length).rewrite
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Spoom
5
- VERSION = "1.7.14"
5
+ VERSION = "1.7.16"
6
6
  end
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 { params(ruby_contents: ::String, file: ::String, max_line_length: T.nilable(::Integer)).returns(::String) }
2855
- def rbs_comments_to_sorbet_sigs(ruby_contents, file:, max_line_length: T.unsafe(nil)); end
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 { params(ruby_contents: ::String, file: ::String, max_line_length: T.nilable(::Integer)).void }
2894
- def initialize(ruby_contents, file:, max_line_length: T.unsafe(nil)); end
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(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spoom
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.14
4
+ version: 1.7.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Terrasa