spoom 1.5.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff274fd4bbf10a58317e281a8e570908fc9827ff74799cb2380d3b90f032240e
4
- data.tar.gz: 16d95b8b1311eade324cbd6086b7f4fd2a30a760478582571cc6f88768a593d5
3
+ metadata.gz: c57bde6d573107ed349529cec1a28b5ed44b88321807d12ce2bfbda61fd4622f
4
+ data.tar.gz: c4ab57e99491657167aa6fcaccdcc4d5933e89ebc6fa6f6830e536d63a71015a
5
5
  SHA512:
6
- metadata.gz: 90b22051252521cf38c3ed26a4e728526e57e190f0f1cd8aca1f2574f9f6219f608f28905ab4fb6dca1d54e04834182e470f9da779d664f205d269fb6ac4c0e4
7
- data.tar.gz: 3aa88be0f3edb8f2cc32d89e76ff83833f295204d3739424c6ea35dbe48db4df41a325771f840451b6bea1ed5e329ee2efaefd2c503a76ee483590f751a3017a
6
+ metadata.gz: 87c9694810a1faef1994380c7b40ab45f3974bfc7d94756d11dafb6a9041f2f999c4807a65128aecea15281d7348e90f650d80fc629eafc433fdec9ff391d8bb
7
+ data.tar.gz: 6c37a954956d9e937fd1f56cb58b2839f094a6ee610108aed82dbb3b6ac1b074ed2356275eafd3e48b9f0259ffe9b9dfc4666f4f52d96f0c7444c4449741ac6e
@@ -0,0 +1,90 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "spoom/sorbet/sigs"
5
+
6
+ module Spoom
7
+ module Cli
8
+ module Srb
9
+ class Sigs < Thor
10
+ extend T::Sig
11
+ include Helper
12
+
13
+ desc "translate", "Translate signatures from/to RBI and RBS"
14
+ option :from, type: :string, aliases: :f, desc: "From format", enum: ["rbi"], default: "rbi"
15
+ option :to, type: :string, aliases: :t, desc: "To format", enum: ["rbs"], default: "rbs"
16
+ def translate(*paths)
17
+ from = options[:from]
18
+ to = options[:to]
19
+ files = collect_files(paths)
20
+
21
+ say("Translating signatures from `#{from}` to `#{to}` " \
22
+ "in `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n")
23
+
24
+ transformed_files = transform_files(files) do |_file, contents|
25
+ Spoom::Sorbet::Sigs.rbi_to_rbs(contents)
26
+ end
27
+
28
+ say("Translated signatures in `#{transformed_files}` file#{transformed_files == 1 ? "" : "s"}.")
29
+ end
30
+
31
+ desc "strip", "Strip all the signatures from the files"
32
+ def strip(*paths)
33
+ files = collect_files(paths)
34
+
35
+ say("Stripping signatures from `#{files.size}` file#{files.size == 1 ? "" : "s"}...\n\n")
36
+
37
+ transformed_files = transform_files(files) do |_file, contents|
38
+ Spoom::Sorbet::Sigs.strip(contents)
39
+ end
40
+
41
+ say("Stripped signatures from `#{transformed_files}` file#{transformed_files == 1 ? "" : "s"}.")
42
+ end
43
+
44
+ no_commands do
45
+ def collect_files(paths)
46
+ paths << "." if paths.empty?
47
+
48
+ files = paths.flat_map do |path|
49
+ if File.file?(path)
50
+ [path]
51
+ else
52
+ Dir.glob("#{path}/**/*.rb")
53
+ end
54
+ end
55
+
56
+ if files.empty?
57
+ say_error("No files to transform")
58
+ exit(1)
59
+ end
60
+
61
+ files
62
+ end
63
+
64
+ def transform_files(files, &block)
65
+ transformed_count = 0
66
+
67
+ files.each do |file|
68
+ contents = File.read(file)
69
+ first_line = contents.lines.first
70
+
71
+ if first_line&.start_with?("# encoding:")
72
+ encoding = T.must(first_line).gsub(/^#\s*encoding:\s*/, "").strip
73
+ contents = contents.force_encoding(encoding)
74
+ end
75
+
76
+ contents = block.call(file, contents)
77
+ File.write(file, contents)
78
+ transformed_count += 1
79
+ rescue RBI::Error => error
80
+ say_warning("Can't parse #{file}: #{error.message}")
81
+ next
82
+ end
83
+
84
+ transformed_count
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
data/lib/spoom/cli/srb.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  require_relative "srb/bump"
5
5
  require_relative "srb/coverage"
6
6
  require_relative "srb/lsp"
7
+ require_relative "srb/sigs"
7
8
  require_relative "srb/tc"
8
9
 
9
10
  module Spoom
@@ -19,6 +20,9 @@ module Spoom
19
20
  desc "bump", "Change Sorbet sigils from one strictness to another when no errors"
20
21
  subcommand "bump", Spoom::Cli::Srb::Bump
21
22
 
23
+ desc "sigs", "Translate signatures from/to RBI and RBS"
24
+ subcommand "sigs", Spoom::Cli::Srb::Sigs
25
+
22
26
  desc "tc", "Run typechecking with advanced options"
23
27
  subcommand "tc", Spoom::Cli::Srb::Tc
24
28
  end
@@ -0,0 +1,173 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rbi"
5
+
6
+ module Spoom
7
+ module Sorbet
8
+ class Sigs
9
+ class << self
10
+ extend T::Sig
11
+
12
+ sig { params(ruby_contents: String).returns(String) }
13
+ def strip(ruby_contents)
14
+ sigs = collect_sigs(ruby_contents)
15
+ lines_to_strip = sigs.flat_map { |sig, _| (sig.loc&.begin_line..sig.loc&.end_line).to_a }
16
+
17
+ lines = []
18
+ ruby_contents.lines.each_with_index do |line, index|
19
+ lines << line unless lines_to_strip.include?(index + 1)
20
+ end
21
+ lines.join
22
+ end
23
+
24
+ sig { params(ruby_contents: String).returns(String) }
25
+ def rbi_to_rbs(ruby_contents)
26
+ ruby_contents = ruby_contents.dup
27
+ sigs = collect_sigs(ruby_contents)
28
+
29
+ sigs.each do |sig, node|
30
+ scanner = Scanner.new(ruby_contents)
31
+ start_index = scanner.find_char_position(
32
+ T.must(sig.loc&.begin_line&.pred),
33
+ T.must(sig.loc).begin_column,
34
+ )
35
+ end_index = scanner.find_char_position(
36
+ sig.loc&.end_line&.pred,
37
+ T.must(sig.loc).end_column,
38
+ )
39
+ ruby_contents[start_index...end_index] = SigTranslator.translate(sig, node)
40
+ end
41
+
42
+ ruby_contents
43
+ end
44
+
45
+ private
46
+
47
+ sig { params(ruby_contents: String).returns(T::Array[[RBI::Sig, T.any(RBI::Method, RBI::Attr)]]) }
48
+ def collect_sigs(ruby_contents)
49
+ tree = RBI::Parser.parse_string(ruby_contents)
50
+ visitor = SigsLocator.new
51
+ visitor.visit(tree)
52
+ visitor.sigs.sort_by { |sig, _rbs_string| -T.must(sig.loc&.begin_line) }
53
+ end
54
+ end
55
+
56
+ class SigsLocator < RBI::Visitor
57
+ extend T::Sig
58
+
59
+ sig { returns(T::Array[[RBI::Sig, T.any(RBI::Method, RBI::Attr)]]) }
60
+ attr_reader :sigs
61
+
62
+ sig { void }
63
+ def initialize
64
+ super
65
+ @sigs = T.let([], T::Array[[RBI::Sig, T.any(RBI::Method, RBI::Attr)]])
66
+ end
67
+
68
+ sig { override.params(node: T.nilable(RBI::Node)).void }
69
+ def visit(node)
70
+ return unless node
71
+
72
+ case node
73
+ when RBI::Method, RBI::Attr
74
+ node.sigs.each do |sig|
75
+ @sigs << [sig, node]
76
+ end
77
+ when RBI::Tree
78
+ visit_all(node.nodes)
79
+ end
80
+ end
81
+ end
82
+
83
+ class SigTranslator
84
+ class << self
85
+ extend T::Sig
86
+
87
+ sig { params(sig: RBI::Sig, node: T.any(RBI::Method, RBI::Attr)).returns(String) }
88
+ def translate(sig, node)
89
+ case node
90
+ when RBI::Method
91
+ translate_method_sig(sig, node)
92
+ when RBI::Attr
93
+ translate_attr_sig(sig, node)
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ sig { params(sig: RBI::Sig, node: RBI::Method).returns(String) }
100
+ def translate_method_sig(sig, node)
101
+ out = StringIO.new
102
+ p = RBI::RBSPrinter.new(out: out, indent: sig.loc&.begin_column)
103
+
104
+ if node.sigs.any?(&:is_final)
105
+ p.printn("# @final")
106
+ p.printt
107
+ end
108
+
109
+ if node.sigs.any?(&:is_abstract)
110
+ p.printn("# @abstract")
111
+ p.printt
112
+ end
113
+
114
+ if node.sigs.any?(&:is_override)
115
+ if node.sigs.any?(&:allow_incompatible_override)
116
+ p.printn("# @override(allow_incompatible: true)")
117
+ else
118
+ p.printn("# @override")
119
+ end
120
+ p.printt
121
+ end
122
+
123
+ if node.sigs.any?(&:is_overridable)
124
+ p.printn("# @overridable")
125
+ p.printt
126
+ end
127
+
128
+ p.print("#: ")
129
+ p.send(:print_method_sig, node, sig)
130
+
131
+ out.string
132
+ end
133
+
134
+ sig { params(sig: RBI::Sig, node: RBI::Attr).returns(String) }
135
+ def translate_attr_sig(sig, node)
136
+ out = StringIO.new
137
+ p = RBI::RBSPrinter.new(out: out)
138
+ p.print_attr_sig(node, sig)
139
+ "#: #{out.string}"
140
+ end
141
+ end
142
+ end
143
+
144
+ # From https://github.com/Shopify/ruby-lsp/blob/9154bfc6ef/lib/ruby_lsp/document.rb#L127
145
+ class Scanner
146
+ extend T::Sig
147
+
148
+ LINE_BREAK = T.let(0x0A, Integer)
149
+
150
+ sig { params(source: String).void }
151
+ def initialize(source)
152
+ @current_line = T.let(0, Integer)
153
+ @pos = T.let(0, Integer)
154
+ @source = T.let(source.codepoints, T::Array[Integer])
155
+ end
156
+
157
+ # Finds the character index inside the source string for a given line and column
158
+ sig { params(line: Integer, character: Integer).returns(Integer) }
159
+ def find_char_position(line, character)
160
+ # Find the character index for the beginning of the requested line
161
+ until @current_line == line
162
+ @pos += 1 until LINE_BREAK == @source[@pos]
163
+ @pos += 1
164
+ @current_line += 1
165
+ end
166
+
167
+ # The final position is the beginning of the line plus the requested column
168
+ @pos + character
169
+ end
170
+ end
171
+ end
172
+ end
173
+ 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.5.0"
5
+ VERSION = "1.5.1"
6
6
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spoom
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandre Terrasa
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-30 00:00:00.000000000 Z
10
+ date: 2025-01-15 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -122,7 +121,6 @@ dependencies:
122
121
  - - ">="
123
122
  - !ruby/object:Gem::Version
124
123
  version: 0.19.2
125
- description:
126
124
  email:
127
125
  - ruby@shopify.com
128
126
  executables:
@@ -144,6 +142,7 @@ files:
144
142
  - lib/spoom/cli/srb/bump.rb
145
143
  - lib/spoom/cli/srb/coverage.rb
146
144
  - lib/spoom/cli/srb/lsp.rb
145
+ - lib/spoom/cli/srb/sigs.rb
147
146
  - lib/spoom/cli/srb/tc.rb
148
147
  - lib/spoom/colors.rb
149
148
  - lib/spoom/context.rb
@@ -207,6 +206,7 @@ files:
207
206
  - lib/spoom/sorbet/lsp/structures.rb
208
207
  - lib/spoom/sorbet/metrics.rb
209
208
  - lib/spoom/sorbet/sigils.rb
209
+ - lib/spoom/sorbet/sigs.rb
210
210
  - lib/spoom/timeline.rb
211
211
  - lib/spoom/version.rb
212
212
  - lib/spoom/visitor.rb
@@ -218,7 +218,6 @@ licenses:
218
218
  - MIT
219
219
  metadata:
220
220
  allowed_push_host: https://rubygems.org
221
- post_install_message:
222
221
  rdoc_options: []
223
222
  require_paths:
224
223
  - lib
@@ -233,8 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
233
232
  - !ruby/object:Gem::Version
234
233
  version: '0'
235
234
  requirements: []
236
- rubygems_version: 3.5.18
237
- signing_key:
235
+ rubygems_version: 3.6.2
238
236
  specification_version: 4
239
237
  summary: Useful tools for Sorbet enthusiasts.
240
238
  test_files: []