sorbet-eraser 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -1
- data/Gemfile.lock +3 -2
- data/README.md +54 -6
- data/lib/sorbet/eraser/parser.rb +149 -18
- data/lib/sorbet/eraser/patterns.rb +77 -58
- data/lib/sorbet/eraser/version.rb +1 -1
- data/lib/t/props.rb +11 -11
- data/lib/t.rb +28 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbfc0c7e028bd4e712e64d29a7c74e65d45937087a3f510f01afab434250b3d5
|
4
|
+
data.tar.gz: 6a89286f19c7ae6ed8e838beba49fcb799426fb14a58ed4b5c3b32d9b59935dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efe378c67fa4749ac7461496d895c69e89ef28ca1f71d6c9385952a96320e8849a6ed15604d8219560903d047b0f9a1eda60fb938b07dc633f55da95a1082119
|
7
|
+
data.tar.gz: af3a02739fbbfdec2fd98c1bbb749b74a2a7f088c237f339966b186ad4b565e8cd353bac2bd4989f2fba8ae7435a096a058c4aca5cded0f209113826e284e994
|
data/CHANGELOG.md
CHANGED
@@ -6,11 +6,35 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [0.3.1] - 2023-06-27
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Shims for `T::Configuration`, `T::Private::RuntimeLevels`, and `T::Methods`.
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
- Fixed various parsing bugs due to incorrect location.
|
18
|
+
|
19
|
+
## [0.3.0] - 2023-06-27
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
- Support for the `default` and `without_accessors` options for `T::Struct`.
|
24
|
+
|
25
|
+
## [0.2.0] - 2023-06-26
|
26
|
+
|
27
|
+
### Added
|
28
|
+
|
29
|
+
- Better support for `T::Struct` subclasses.
|
30
|
+
|
9
31
|
## [0.1.1] - 2021-11-17
|
10
32
|
|
11
33
|
### Changed
|
12
34
|
|
13
35
|
- Require MFA for releasing.
|
14
36
|
|
15
|
-
[unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.
|
37
|
+
[unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.0...HEAD
|
38
|
+
[0.3.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.2.0...v0.3.0
|
39
|
+
[0.2.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.1.1...v0.2.0
|
16
40
|
[0.1.1]: https://github.com/kddnewton/sorbet-eraser/compare/f6a712...v0.1.1
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sorbet-eraser (0.
|
4
|
+
sorbet-eraser (0.3.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -10,6 +10,7 @@ GEM
|
|
10
10
|
rake (13.0.6)
|
11
11
|
|
12
12
|
PLATFORMS
|
13
|
+
arm64-darwin-21
|
13
14
|
arm64-darwin-22
|
14
15
|
x86_64-darwin-19
|
15
16
|
x86_64-darwin-21
|
@@ -22,4 +23,4 @@ DEPENDENCIES
|
|
22
23
|
sorbet-eraser!
|
23
24
|
|
24
25
|
BUNDLED WITH
|
25
|
-
2.
|
26
|
+
2.4.12
|
data/README.md
CHANGED
@@ -5,9 +5,37 @@
|
|
5
5
|
|
6
6
|
Erase all traces of `sorbet-runtime` code.
|
7
7
|
|
8
|
-
|
8
|
+
[Sorbet](https://sorbet.org/) is a type checker for Ruby. To annotate types in your Ruby code, you use constructs like `sig` and `T.let`. Sorbet then uses a static analysis tool to check that your code is type safe. At runtime, these types are enforced by the `sorbet-runtime` gem that provides implementations of all of these constructs.
|
9
9
|
|
10
|
-
|
10
|
+
Sometimes, you want to use Sorbet for development, but don't want to run `sorbet-runtime` in production. This may be because you have a performance-critical application, or because you're writing a library and you don't want to impose a runtime dependency on your users.
|
11
|
+
|
12
|
+
To handle these use cases, `sorbet-eraser` provides a way to erase all traces of `sorbet-runtime` code from your source code. This means that you can use Sorbet for development, but not have to worry about `sorbet-runtime` in production. For example,
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class HelloWorld
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
sig { returns(String) }
|
19
|
+
def hello
|
20
|
+
T.let("World!", String)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
will be transformed into
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class HelloWorld
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
def hello
|
33
|
+
"World!"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Notice that the `extend T::Sig` and `sig` constructs have been removed from your source code. Notice also that all line and column information has been preserved 1:1, so that stack traces and tracepoints will still be accurate.
|
11
39
|
|
12
40
|
## Installation
|
13
41
|
|
@@ -27,17 +55,37 @@ Or install it yourself as:
|
|
27
55
|
|
28
56
|
## Usage
|
29
57
|
|
30
|
-
There are two ways to use this gem, depending on your needs.
|
58
|
+
There are two ways to use this gem, depending on your needs. You can erase `sorbet-runtime` code ahead of time or just in time.
|
31
59
|
|
32
|
-
|
60
|
+
### Ahead of time
|
33
61
|
|
34
|
-
|
62
|
+
To erase `sorbet-runtime` code ahead of time, you would either use the CLI provided with this gem or the Ruby API. With the CLI, you would run:
|
35
63
|
|
36
64
|
```bash
|
37
65
|
bundle exec sorbet-eraser '**/*.rb'
|
38
66
|
```
|
39
67
|
|
40
|
-
It accepts any number of filepaths/patterns on the command line and will modify the source files with their erased contents.
|
68
|
+
It accepts any number of filepaths/patterns on the command line and will modify the source files in place with their erased contents. If you would instead prefer to script it yourself using the Ruby API, you would run:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Sorbet::Eraser.erase(source)
|
72
|
+
```
|
73
|
+
|
74
|
+
where `source` is a string that represents valid Ruby code.
|
75
|
+
|
76
|
+
### Just in time
|
77
|
+
|
78
|
+
If you're looking to avoid a build step like the one described above, you can instead erase your code immediately before it is compiled by the Ruby virtual machine. To do, call:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require "sorbet/eraser/autoload"
|
82
|
+
```
|
83
|
+
|
84
|
+
as soon as possible when your application is first booting. This will hook into the autoload process to erase all `sorbet-runtime` code before it gets passed to Ruby to parse. Note that the tradeoff here is that it eliminates the need for a build step, but slows down your parse/boot time.
|
85
|
+
|
86
|
+
### Runtime structures
|
87
|
+
|
88
|
+
If you used any runtime structures like `T::Struct` or `T::Enum` you'll need a runtime shim. We provide very basic versions of these in the `sorbet-eraser` gem, and they are required automatically.
|
41
89
|
|
42
90
|
### Status
|
43
91
|
|
data/lib/sorbet/eraser/parser.rb
CHANGED
@@ -59,7 +59,10 @@ module Sorbet
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def to_s
|
62
|
-
@
|
62
|
+
@repr ||= begin
|
63
|
+
children = body.map { |child| child.is_a?(Array) ? child.map(&:to_s) : child }
|
64
|
+
"<#{event} #{children.join(" ")}>"
|
65
|
+
end
|
63
66
|
end
|
64
67
|
end
|
65
68
|
|
@@ -67,7 +70,7 @@ module Sorbet
|
|
67
70
|
class ParsingError < StandardError
|
68
71
|
end
|
69
72
|
|
70
|
-
attr_reader :source, :line_counts, :errors, :patterns
|
73
|
+
attr_reader :source, :line_counts, :errors, :patterns, :heredocs
|
71
74
|
|
72
75
|
def initialize(source)
|
73
76
|
super(source)
|
@@ -88,6 +91,7 @@ module Sorbet
|
|
88
91
|
|
89
92
|
@errors = []
|
90
93
|
@patterns = []
|
94
|
+
@heredocs = []
|
91
95
|
end
|
92
96
|
|
93
97
|
def self.erase(source)
|
@@ -108,11 +112,146 @@ module Sorbet
|
|
108
112
|
line_counts[lineno - 1][column]
|
109
113
|
end
|
110
114
|
|
115
|
+
def find_loc(args)
|
116
|
+
ranges = []
|
117
|
+
|
118
|
+
args.each do |arg|
|
119
|
+
case arg
|
120
|
+
when Node
|
121
|
+
ranges << arg.range if arg.range
|
122
|
+
when Array
|
123
|
+
ranges << find_loc(arg)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
case ranges.length
|
128
|
+
when 0
|
129
|
+
nil
|
130
|
+
when 1
|
131
|
+
ranges.first
|
132
|
+
else
|
133
|
+
ranges.first.begin...ranges.last.end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Better location information for aref.
|
138
|
+
def on_aref(recv, arg)
|
139
|
+
rend = arg.range.end + source[arg.range.end..].index("]") + 1
|
140
|
+
Node.new(:aref, [recv, arg], recv.range.begin...rend)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Better location information for arg_paren.
|
144
|
+
def on_arg_paren(arg)
|
145
|
+
if arg
|
146
|
+
rbegin = source[..arg.range.begin].rindex("(")
|
147
|
+
rend = arg.range.end + source[arg.range.end..].index(")") + 1
|
148
|
+
Node.new(:arg_paren, [arg], rbegin...rend)
|
149
|
+
else
|
150
|
+
segment = source[..loc]
|
151
|
+
Node.new(:arg_paren, [arg], segment.rindex("(")...(segment.rindex(")") + 1))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
LISTS = { qsymbols: "%i", qwords: "%w", symbols: "%I", words: "%W" }.freeze
|
156
|
+
TERMINATORS = { "[" => "]", "{" => "}", "(" => ")", "<" => ">" }.freeze
|
157
|
+
|
158
|
+
# Better location information for array.
|
159
|
+
def on_array(arg)
|
160
|
+
case arg&.event
|
161
|
+
when nil
|
162
|
+
segment = source[..loc]
|
163
|
+
Node.new(:array, [arg], segment.rindex("[")...(segment.rindex("]") + 1))
|
164
|
+
when :qsymbols, :qwords, :symbols, :words
|
165
|
+
rbegin = source[...arg.range.begin].rindex(LISTS.fetch(arg.event))
|
166
|
+
rend = source[arg.range.end..].index(TERMINATORS.fetch(source[rbegin + 2]) { source[rbegin + 2] }) + arg.range.end + 1
|
167
|
+
Node.new(:array, [arg], rbegin...rend)
|
168
|
+
else
|
169
|
+
Node.new(:array, [arg], arg.range)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Better location information for brace_block.
|
174
|
+
def on_brace_block(params, body)
|
175
|
+
if params || body.range
|
176
|
+
rbegin = source[...(params || body).range.begin].rindex("{")
|
177
|
+
|
178
|
+
rend = body.range&.end || params.range.end
|
179
|
+
rend = rend + source[rend..].index("}") + 1
|
180
|
+
|
181
|
+
Node.new(:brace_block, [params, body], rbegin...rend)
|
182
|
+
else
|
183
|
+
segment = source[..loc]
|
184
|
+
Node.new(:brace_block, [params, body], segment.rindex("{")...(segment.rindex("}") + 1))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Better location information for do_block.
|
189
|
+
def on_do_block(params, body)
|
190
|
+
if params || body.range
|
191
|
+
rbegin = source[...(params || body).range.begin].rindex("do")
|
192
|
+
|
193
|
+
rend = body.range&.end || params.range.end
|
194
|
+
rend = rend + source[rend..].index("end") + 3
|
195
|
+
|
196
|
+
Node.new(:do_block, [params, body], rbegin...rend)
|
197
|
+
else
|
198
|
+
segment = source[..loc]
|
199
|
+
Node.new(:do_block, [params, body], segment.rindex("do")...(segment.rindex("end") + 3))
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Better location information for hash.
|
204
|
+
def on_hash(arg)
|
205
|
+
if arg
|
206
|
+
Node.new(:hash, [arg], arg.range)
|
207
|
+
else
|
208
|
+
segment = source[..loc]
|
209
|
+
Node.new(:hash, [arg], segment.rindex("{")...(segment.rindex("}") + 1))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Track the open heredocs so we can replace the string literal ranges with
|
214
|
+
# the range of their declarations.
|
215
|
+
def on_heredoc_beg(value)
|
216
|
+
range = loc.then { |start| start...(start + value.size) }
|
217
|
+
heredocs << [range, value, nil]
|
218
|
+
|
219
|
+
Node.new(:@heredoc_beg, [value], range)
|
220
|
+
end
|
221
|
+
|
222
|
+
# If a heredoc ends, then the next string literal event will be the
|
223
|
+
# heredoc.
|
224
|
+
def on_heredoc_end(value)
|
225
|
+
range = loc.then { |start| start...(start + value.size) }
|
226
|
+
heredocs.find { |(_, beg_arg, end_arg)| beg_arg.include?(value.strip) && end_arg.nil? }[2] = value
|
227
|
+
|
228
|
+
Node.new(:@heredoc_end, [value], range)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Track the parsing errors for nicer error messages.
|
232
|
+
def on_parse_error(error)
|
233
|
+
errors << "line #{lineno}: #{error}"
|
234
|
+
end
|
235
|
+
|
236
|
+
# Better location information for string_literal taking into account
|
237
|
+
# heredocs.
|
238
|
+
def on_string_literal(arg)
|
239
|
+
if heredoc = heredocs.find { |(_, _, end_arg)| end_arg }
|
240
|
+
Node.new(:string_literal, [arg], heredocs.delete(heredoc)[0])
|
241
|
+
else
|
242
|
+
Node.new(:string_literal, [arg], arg.range)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
handled = private_instance_methods(false)
|
247
|
+
|
111
248
|
# Loop through all of the scanner events and define a basic method that
|
112
249
|
# wraps everything into a node class.
|
113
250
|
SCANNER_EVENTS.each do |event|
|
251
|
+
next if handled.include?(:"on_#{event}")
|
252
|
+
|
114
253
|
define_method(:"on_#{event}") do |value|
|
115
|
-
range = loc.then { |start| start
|
254
|
+
range = loc.then { |start| start...(start + (value&.size || 0)) }
|
116
255
|
Node.new(:"@#{event}", [value], range)
|
117
256
|
end
|
118
257
|
end
|
@@ -122,41 +261,33 @@ module Sorbet
|
|
122
261
|
# it's an _add method then just append to the array. If it's a normal
|
123
262
|
# method, then create a new node and determine its bounds.
|
124
263
|
PARSER_EVENT_TABLE.each do |event, arity|
|
264
|
+
next if handled.include?(:"on_#{event}")
|
265
|
+
|
125
266
|
if event =~ /\A(.+)_new\z/ && event != :assoc_new
|
126
267
|
prefix = $1.to_sym
|
127
268
|
|
128
269
|
define_method(:"on_#{event}") do
|
129
|
-
Node.new(prefix, [],
|
270
|
+
Node.new(prefix, [], nil)
|
130
271
|
end
|
131
272
|
elsif event =~ /_add\z/
|
132
273
|
define_method(:"on_#{event}") do |node, value|
|
133
274
|
range =
|
134
275
|
if node.body.empty?
|
135
276
|
value.range
|
277
|
+
elsif node.range && value.range
|
278
|
+
(node.range.begin...value.range.end)
|
136
279
|
else
|
137
|
-
|
280
|
+
node.range || value.range
|
138
281
|
end
|
139
282
|
|
140
283
|
node.class.new(node.event, node.body + [value], range)
|
141
284
|
end
|
142
|
-
elsif event == :parse_error
|
143
|
-
# skip this, as we're going to define it below
|
144
285
|
else
|
145
286
|
define_method(:"on_#{event}") do |*args|
|
146
|
-
|
147
|
-
|
148
|
-
first ||= loc.then { |start| start..start }
|
149
|
-
last ||= first
|
150
|
-
|
151
|
-
Node.new(event, args, first.begin..[last.end, loc].max)
|
287
|
+
Node.new(event, args, find_loc(args))
|
152
288
|
end
|
153
289
|
end
|
154
290
|
end
|
155
|
-
|
156
|
-
# Track the parsing errors for nicer error messages.
|
157
|
-
def on_parse_error(error)
|
158
|
-
errors << "line #{lineno}: #{error}"
|
159
|
-
end
|
160
291
|
end
|
161
292
|
end
|
162
293
|
end
|
@@ -101,69 +101,80 @@ module Sorbet
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def on_method_add_arg(call, arg_paren)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
104
|
+
if call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:must|reveal_type|unsafe)>>\z/) && arg_paren.match?(/\A<arg_paren <args_add_block <args .+> false>>\z/)
|
105
|
+
# T.must(foo)
|
106
|
+
# T.reveal_type(foo)
|
107
|
+
# T.unsafe(foo)
|
109
108
|
patterns << TOneArgMethodCallParensPattern.new(call.range.begin...arg_paren.range.end)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
)
|
132
|
-
end
|
133
|
-
|
134
|
-
# abstract!
|
135
|
-
# final!
|
136
|
-
# interface!
|
137
|
-
if call.match?(/<fcall <@ident (?:abstract|final|interface)!>>/) &&
|
138
|
-
arg_paren.match?("<args >")
|
139
|
-
patterns << DeclarationPattern.new(call.range.begin...arg_paren.range.end)
|
140
|
-
end
|
141
|
-
|
142
|
-
# mixes_in_class_methods(foo)
|
143
|
-
if call.match?("<fcall <@ident mixes_in_class_methods>>") &&
|
144
|
-
arg_paren.match?(/<arg_paren <args_add_block <args <.+>>> false>>/)
|
109
|
+
elsif call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>\z/) && arg_paren.match?(/\A<arg_paren <args_add_block <args .+> false>>\z/)
|
110
|
+
# T.assert_type!(foo, bar)
|
111
|
+
# T.cast(foo, bar)
|
112
|
+
# T.let(foo, bar)
|
113
|
+
patterns << TTwoArgMethodCallParensPattern.new(
|
114
|
+
call.range.begin...arg_paren.range.end,
|
115
|
+
comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
|
116
|
+
)
|
117
|
+
elsif call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident bind>>\z/) && arg_paren.match?(/\A<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>\z/)
|
118
|
+
# T.bind(self, foo)
|
119
|
+
patterns << TTwoArgMethodCallParensPattern.new(
|
120
|
+
call.range.begin...arg_paren.range.end,
|
121
|
+
comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
|
122
|
+
)
|
123
|
+
elsif call.match?(/\A<fcall <@ident (?:abstract|final|interface)!>>\z/) && arg_paren.match?("<args >")
|
124
|
+
# abstract!
|
125
|
+
# final!
|
126
|
+
# interface!
|
127
|
+
patterns << DeclarationPattern.new(call.range)
|
128
|
+
elsif call.match?("<fcall <@ident mixes_in_class_methods>>") && arg_paren.match?(/\A<arg_paren <args_add_block <args <.+>>> false>>\z/)
|
129
|
+
# mixes_in_class_methods(foo)
|
145
130
|
patterns << MixesInClassMethodsPattern.new(call.range.begin...arg_paren.range.end)
|
146
131
|
end
|
147
132
|
|
148
133
|
super
|
149
134
|
end
|
150
135
|
|
151
|
-
# prop :foo, String
|
152
|
-
# const :foo, String
|
153
|
-
class
|
136
|
+
# prop :foo, String => prop :foo
|
137
|
+
# const :foo, String => const :foo
|
138
|
+
class PropWithoutOptionsPattern < Pattern
|
154
139
|
def replace(segment)
|
155
|
-
segment.
|
156
|
-
|
140
|
+
segment.dup.tap do |replacement|
|
141
|
+
range = metadata.fetch(:comma)..-1
|
142
|
+
replacement[range] = blank(replacement[range])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# prop :foo, String, default: "" => prop :foo, default: ""
|
148
|
+
# const :foo, String, default: "" => const :foo, default: ""
|
149
|
+
class PropWithOptionsPattern < Pattern
|
150
|
+
def replace(segment)
|
151
|
+
segment.dup.tap do |replacement|
|
152
|
+
first_comma = metadata.fetch(:first_comma)
|
153
|
+
second_comma = metadata.fetch(:second_comma)
|
154
|
+
|
155
|
+
range = (first_comma + 1)..second_comma
|
156
|
+
replacement[range] = blank(replacement[range])
|
157
157
|
end
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
161
161
|
def on_command(ident, args_add_block)
|
162
|
-
if ident.match?(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
patterns <<
|
162
|
+
if ident.match?(/\A<@ident (?:const|prop)>\z/)
|
163
|
+
if args_add_block.match?(/\A<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> <bare_assoc_hash .+> false>\z/)
|
164
|
+
# prop :foo, String, default: ""
|
165
|
+
# const :foo, String, default: ""
|
166
|
+
patterns << PropWithOptionsPattern.new(
|
167
|
+
ident.range.begin..args_add_block.range.end,
|
168
|
+
first_comma: args_add_block.body[0].body[0].range.end - ident.range.begin,
|
169
|
+
second_comma: args_add_block.body[0].body[1].range.end - ident.range.begin
|
170
|
+
)
|
171
|
+
elsif args_add_block.match?(/\A<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> false>\z/)
|
172
|
+
# prop :foo, String
|
173
|
+
# const :foo, String
|
174
|
+
patterns << PropWithoutOptionsPattern.new(
|
175
|
+
ident.range.begin..args_add_block.range.end,
|
176
|
+
comma: args_add_block.body[0].body[0].range.end - ident.range.begin
|
177
|
+
)
|
167
178
|
end
|
168
179
|
end
|
169
180
|
|
@@ -171,15 +182,11 @@ module Sorbet
|
|
171
182
|
end
|
172
183
|
|
173
184
|
def on_command_call(var_ref, period, ident, args_add_block)
|
174
|
-
if var_ref.match?("<var_ref <@const T>>") && period.match?("<@period .>")
|
185
|
+
if var_ref.match?("<var_ref <@const T>>") && period.match?("<@period .>") && ident.match?(/\A<@ident (?:must|reveal_type|unsafe)>\z/) && args_add_block.match?(/\A<args_add_block <args <.+>> false>\z/) && args_add_block.body[0].body.length == 1
|
175
186
|
# T.must foo
|
176
187
|
# T.reveal_type foo
|
177
188
|
# T.unsafe foo
|
178
|
-
|
179
|
-
args_add_block.match?(/<args_add_block <args <.+>> false>/) &&
|
180
|
-
args_add_block.body[0].body.length == 1
|
181
|
-
patterns << TMustNoParensPattern.new(var_ref.range.begin...args_add_block.range.end)
|
182
|
-
end
|
189
|
+
patterns << TMustNoParensPattern.new(var_ref.range.begin..args_add_block.range.end)
|
183
190
|
end
|
184
191
|
|
185
192
|
super
|
@@ -194,10 +201,22 @@ module Sorbet
|
|
194
201
|
end
|
195
202
|
end
|
196
203
|
|
204
|
+
# sig do foo end =>
|
205
|
+
class SigBlockPattern < Pattern
|
206
|
+
def replace(segment)
|
207
|
+
segment.gsub(/(sig\s*do.+end)(.*)/m) do
|
208
|
+
"#{blank($1)}#{$2}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
197
213
|
def on_stmts_add(node, value)
|
198
|
-
|
199
|
-
|
214
|
+
if value.match?(/\A<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>\z/)
|
215
|
+
# sig { foo }
|
200
216
|
patterns << SigBracesPattern.new(value.range)
|
217
|
+
elsif value.match?(/\A<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <do_block <bodystmt .+>>>\z/)
|
218
|
+
# sig do foo end
|
219
|
+
patterns << SigBlockPattern.new(value.range)
|
201
220
|
end
|
202
221
|
|
203
222
|
super
|
data/lib/t/props.rb
CHANGED
@@ -20,31 +20,31 @@ module T
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def prop(name, rules = {})
|
23
|
-
create_prop(name)
|
24
|
-
attr_accessor
|
23
|
+
create_prop(name, rules)
|
24
|
+
attr_accessor(name) unless rules[:without_accessors]
|
25
25
|
end
|
26
26
|
|
27
27
|
def const(name, rules = {})
|
28
|
-
create_prop(name)
|
29
|
-
attr_reader
|
28
|
+
create_prop(name, rules)
|
29
|
+
attr_reader(name) unless rules[:without_accessors]
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
-
def create_prop(name)
|
35
|
-
props << name
|
36
|
-
props.
|
34
|
+
def create_prop(name, rules)
|
35
|
+
props << [name, rules]
|
36
|
+
props.sort_by!(&:first)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
40
|
# Here we're going to check against the props that have been defined on the
|
41
41
|
# class level and set appropriate values.
|
42
42
|
def initialize(hash = {})
|
43
|
-
|
44
|
-
|
45
|
-
else
|
46
|
-
raise ArgumentError, "Expected keys #{self.class.props} but got #{hash.keys.sort}"
|
43
|
+
self.class.props.each do |name, rules|
|
44
|
+
instance_variable_set("@#{name}", hash.key?(name) ? hash.delete(name) : rules[:default])
|
47
45
|
end
|
46
|
+
|
47
|
+
raise ArgumentError, "unknown keyword: #{hash.keys.first}" unless hash.empty?
|
48
48
|
end
|
49
49
|
|
50
50
|
# This module is entirely empty because we haven't implemented anything from
|
data/lib/t.rb
CHANGED
@@ -33,6 +33,34 @@ module T
|
|
33
33
|
module Sig
|
34
34
|
end
|
35
35
|
|
36
|
+
# I really don't want to be shimming this, but there are places where people
|
37
|
+
# try to reference these values.
|
38
|
+
module Private
|
39
|
+
module RuntimeLevels
|
40
|
+
def self.default_checked_level; :never; end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Methods
|
44
|
+
module MethodHooks
|
45
|
+
end
|
46
|
+
|
47
|
+
module SingletonMethodHooks
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.signature_for_method(method); method; end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# I also don't want to shim this, but there are places where people will
|
55
|
+
# reference it.
|
56
|
+
module Configuration
|
57
|
+
class << self
|
58
|
+
attr_accessor :inline_type_error_handler,
|
59
|
+
:call_validation_error_handler,
|
60
|
+
:sig_builder_error_handler
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
36
64
|
# Type aliases don't actually do anything, but they are usually assigned to
|
37
65
|
# constants, so in that case we need to return something.
|
38
66
|
def self.type_alias
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sorbet-eraser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Newton
|
@@ -88,7 +88,7 @@ licenses:
|
|
88
88
|
- MIT
|
89
89
|
metadata:
|
90
90
|
bug_tracker_uri: https://github.com/kddnewton/sorbet-eraser/issues
|
91
|
-
changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.
|
91
|
+
changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.3.1/CHANGELOG.md
|
92
92
|
source_code_uri: https://github.com/kddnewton/sorbet-eraser
|
93
93
|
rubygems_mfa_required: 'true'
|
94
94
|
post_install_message:
|