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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efd5822039bf577e0c4a37171d7e91abd9c7bed250c9d09c779f52faa0d84cf6
4
- data.tar.gz: 1a88eab81b0932ab400f5aeaf08e43cb5107fc08c135e232cc444205f76fdcf3
3
+ metadata.gz: cbfc0c7e028bd4e712e64d29a7c74e65d45937087a3f510f01afab434250b3d5
4
+ data.tar.gz: 6a89286f19c7ae6ed8e838beba49fcb799426fb14a58ed4b5c3b32d9b59935dc
5
5
  SHA512:
6
- metadata.gz: a64889bcbdf6affe397a394ed3a0841a166ff376e806e050967d4676910670a5f75d774e25c2490fae410c2b5e9263f07c07597ae6c0a0c08ae226343032ed0f
7
- data.tar.gz: 50b223fde3f5901535888d6e06f685ba128c6bda051d516fe37f8b825ce9c328bfddaff54bb19a52aa1694309be11a74732ceb0b582e00179cf939d8eba760ba
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.1.1...HEAD
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.2.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.2.15
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
- `sorbet` is a great tool for development. However, in production, it incurs a penalty because it still functions as Ruby code. Even if you completely shim all `sorbet-runtime` method calls (for example by replacing `sig {} ` with a method that immediately returns) you still pay the cost of a method call in the first place.
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
- This gem takes a different approach, but entirely eliminating the `sig` method call (as well as all the other `sorbet-runtime` constructs) from the source before Ruby compiles it.
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
- The first is that you can hook into the Ruby compilation process and do just-in-time erasure. To do this — before any code is loaded that would require a `sorbet-runtime` construct — call `require "sorbet/eraser/autoload"`. This will hook into the autoload process to erase all `sorbet-runtime` code before it gets passed to Ruby to parse. This eliminates the need for a build step, but slows down your parse/boot time.
60
+ ### Ahead of time
33
61
 
34
- The second is that you can preprocess your Ruby files using either the CLI or the Ruby API. With the CLI, you would run:
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. Alternatively, you can programmatically use this gem through the `Sorbet::Eraser.erase(source)` API, where `source` is a string that represents valid Ruby code. Ruby code without the listed constructs will be returned.
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
 
@@ -59,7 +59,10 @@ module Sorbet
59
59
  end
60
60
 
61
61
  def to_s
62
- @to_s ||= "<#{event} #{body.map(&:to_s).join(" ")}>"
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..(start + (value&.size || 0)) }
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, [], loc.then { |start| start..start })
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
- (node.range.begin..value.range.end)
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
- first, *, last = args.grep(Node).map(&:range)
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
- # T.must(foo)
105
- # T.reveal_type(foo)
106
- # T.unsafe(foo)
107
- if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident (?:must|reveal_type|unsafe)>>/) &&
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
- end
111
-
112
- # T.assert_type!(foo, bar)
113
- # T.cast(foo, bar)
114
- # T.let(foo, bar)
115
- if call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>\z/) &&
116
- arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
117
- patterns <<
118
- TTwoArgMethodCallParensPattern.new(
119
- call.range.begin...arg_paren.range.end,
120
- comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
121
- )
122
- end
123
-
124
- # T.bind(self, foo)
125
- if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident bind>>/) &&
126
- arg_paren.match?(/<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>/)
127
- patterns <<
128
- TTwoArgMethodCallParensPattern.new(
129
- call.range.begin...arg_paren.range.end,
130
- comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
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 PropPattern < Pattern
136
+ # prop :foo, String => prop :foo
137
+ # const :foo, String => const :foo
138
+ class PropWithoutOptionsPattern < Pattern
154
139
  def replace(segment)
155
- segment.gsub(/((?:prop|const)\s+:.+),(\s*)([^\n;,]+)(.*)/m) do
156
- "#{$1} #{$2}#{blank($3)}#{$4}"
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?(/<@ident (?:const|prop)>/)
163
- # prop :foo, String
164
- # const :foo, String
165
- if args_add_block.match?(/<args_add_block <args <symbol_literal <symbol <@ident .+>>> <.+> false>/)
166
- patterns << PropPattern.new(ident.range.begin...args_add_block.range.end)
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
- if ident.match?(/<@ident (?:must|reveal_type|unsafe)>/) &&
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
- # sig { foo }
199
- if value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>/)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sorbet
4
4
  module Eraser
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.1"
6
6
  end
7
7
  end
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 name
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 name
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.sort!
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
- if self.class.props == hash.keys.sort
44
- hash.each { |key, value| instance_variable_set("@#{key}", value) }
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.2.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.2.0/CHANGELOG.md
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: