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 +4 -4
- data/lib/spoom/cli/srb/sigs.rb +90 -0
- data/lib/spoom/cli/srb.rb +4 -0
- data/lib/spoom/sorbet/sigs.rb +173 -0
- data/lib/spoom/version.rb +1 -1
- metadata +5 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c57bde6d573107ed349529cec1a28b5ed44b88321807d12ce2bfbda61fd4622f
|
4
|
+
data.tar.gz: c4ab57e99491657167aa6fcaccdcc4d5933e89ebc6fa6f6830e536d63a71015a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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.
|
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:
|
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.
|
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: []
|