spoom 1.5.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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: []