sorbet-eraser 0.3.0 → 0.4.0

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: 5ddbc396f3f164a3d0e67e9cf7046733ce52f792eb0abc17e9f5f056f415cd5e
4
- data.tar.gz: 965ef9955b75bba0a2702ea127d5923d7e4b1a55ed2b2e6dca239970adbb0fe1
3
+ metadata.gz: 3859ee512cb01ed5b84df15af3dedaa629c30d122e80a7436821841f87184562
4
+ data.tar.gz: 8392610b8fb03ea78d2572b684c2ccf9b4806f082043e3b2614550a370eeea13
5
5
  SHA512:
6
- metadata.gz: 5deb006431e8e31ed089fe6e8fba281f3c65487fc6996a853b05bb0d9600ff034232e7319fb44912a69dc89b60b160900b9b503a17991d19da2bf02f5803c5f8
7
- data.tar.gz: a6a20f8aa21b2a2c9e75a4c7e83db5b28556516296d16f55eedd0113da807cbf0c3a827fbeda7eaf9fb95d2a29a9f0c4ddc9090d638e00469989520d2c492f1d
6
+ metadata.gz: 02ee32fc8bcf3d1951ddd9577a4f46c181a01f196eb6c2af34bb8bb0b68a0ebc3fb28515d0ca1e391d2d1ee305d3012aafd943918df643dd2a3a44c5272a38b2
7
+ data.tar.gz: fa7b5e80e3fda49d50df015532110f6b4050450c5348f4d9f0ac6f40c8e2b411882a5348b38e305419b74e0bf6e833b887997bb6757e440fb3acc552e286d7e4
data/CHANGELOG.md CHANGED
@@ -6,6 +6,23 @@ 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.4.0] - 2023-07-03
10
+
11
+ ### Added
12
+
13
+ - `require "t"` now requires a file that only requires `"sorbet/eraser/t"`, so they are effectively the same thing. If you're in a situation where you need to load a different `"t"`, then you can manually require `"sorbet/eraser/t"` and it should work.
14
+ - Replace all `typed:` comments with `typed: ignore`.
15
+
16
+ ## [0.3.1] - 2023-06-27
17
+
18
+ ### Added
19
+
20
+ - Shims for `T::Configuration`, `T::Private::RuntimeLevels`, and `T::Methods`.
21
+
22
+ ### Changed
23
+
24
+ - Fixed various parsing bugs due to incorrect location.
25
+
9
26
  ## [0.3.0] - 2023-06-27
10
27
 
11
28
  ### Added
@@ -24,7 +41,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
24
41
 
25
42
  - Require MFA for releasing.
26
43
 
27
- [unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.0...HEAD
44
+ [unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.4.0...HEAD
45
+ [0.4.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.1...v0.4.0
46
+ [0.3.1]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.0...v0.3.1
28
47
  [0.3.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.2.0...v0.3.0
29
48
  [0.2.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.1.1...v0.2.0
30
49
  [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.3.0)
4
+ sorbet-eraser (0.4.0)
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,41 @@
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
+ # typed: true
16
+
17
+ class HelloWorld
18
+ extend T::Sig
19
+
20
+ sig { returns(String) }
21
+ def hello
22
+ T.let("World!", String)
23
+ end
24
+ end
25
+ ```
26
+
27
+ will be transformed into
28
+
29
+ ```ruby
30
+
31
+
32
+ class HelloWorld
33
+
34
+
35
+
36
+ def hello
37
+ "World!"
38
+ end
39
+ end
40
+ ```
41
+
42
+ 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
43
 
12
44
  ## Installation
13
45
 
@@ -27,47 +59,66 @@ Or install it yourself as:
27
59
 
28
60
  ## Usage
29
61
 
30
- There are two ways to use this gem, depending on your needs.
62
+ 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
63
 
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.
64
+ ### Ahead of time
33
65
 
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:
66
+ 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
67
 
36
68
  ```bash
37
69
  bundle exec sorbet-eraser '**/*.rb'
38
70
  ```
39
71
 
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.
72
+ 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:
73
+
74
+ ```ruby
75
+ Sorbet::Eraser.erase(source)
76
+ ```
77
+
78
+ where `source` is a string that represents valid Ruby code.
79
+
80
+ ### Just in time
81
+
82
+ 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:
83
+
84
+ ```ruby
85
+ require "sorbet/eraser/autoload"
86
+ ```
87
+
88
+ 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.
89
+
90
+ ### Runtime structures
41
91
 
42
- If you used any runtime structures like `T::Struct` or `T::Enum` you'll need a runtime shim. For that, you can add `require "t"` to your codebase, which ships with this gem, or in your gemfile `gem "sorbet-eraser", require: "t"`.
92
+ 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.
43
93
 
44
94
  ### Status
45
95
 
46
96
  Below is a table of the status of each `sorbet-runtime` construct and its current support status.
47
97
 
48
- | Construct | Status | Replacement |
49
- | --------------------------------------------------- | ------ | ----------- |
50
- | `extend T::*` | ✅ | Shimmed |
51
- | `abstract!`, `final!`, `interface!`, `sealed!` | ✅ | Shimmed |
52
- | `mixes_in_class_methods(*)`, `requires_ancestor(*)` | ✅ | Shimmed |
53
- | `type_member(*)`, `type_template(*)` | ✅ | Shimmed |
54
- | `class Foo < T::Enum` | ✅ | Shimmed |
55
- | `class Foo < T::InexactStruct` | 🛠 | Shimmed |
56
- | `class Foo < T::Struct` | 🛠 | Shimmed |
57
- | `class Foo < T::ImmutableStruct` | 🛠 | Shimmed |
58
- | `include T::Props` | 🛠 | Shimmed |
59
- | `include T::Props::Serializable` | 🛠 | Shimmed |
60
- | `include T::Props::Constructor` | 🛠 | Shimmed |
61
- | `sig` | | Removed |
62
- | `T.absurd(foo)` | ✅ | Shimmed |
63
- | `T.assert_type!(foo, bar)` | ✅ | `foo` |
64
- | `T.bind(self, foo)` | ✅ | `self` |
65
- | `T.cast(foo, bar)` | ✅ | `foo` |
66
- | `T.let(foo, bar)` | ✅ | `foo` |
67
- | `T.must(foo)` | ✅ | `foo` |
68
- | `T.reveal_type(foo)` | ✅ | `foo` |
69
- | `T.type_alias { foo }` | ✅ | Shimmed |
70
- | `T.unsafe(foo)` | ✅ | `foo` |
98
+ | Construct | Status | Replacement |
99
+ | --------------------------------------------------- | ------ | ----------------- |
100
+ | `# typed: foo` | ✅ | `# typed: ignore` |
101
+ | `extend T::*` | ✅ | Shimmed |
102
+ | `abstract!`, `final!`, `interface!`, `sealed!` | ✅ | Shimmed |
103
+ | `mixes_in_class_methods(*)`, `requires_ancestor(*)` | ✅ | Shimmed |
104
+ | `type_member(*)`, `type_template(*)` | ✅ | Shimmed |
105
+ | `class Foo < T::Enum` | | Shimmed |
106
+ | `class Foo < T::InexactStruct` | 🛠 | Shimmed |
107
+ | `class Foo < T::Struct` | 🛠 | Shimmed |
108
+ | `class Foo < T::ImmutableStruct` | 🛠 | Shimmed |
109
+ | `include T::Props` | 🛠 | Shimmed |
110
+ | `include T::Props::Serializable` | 🛠 | Shimmed |
111
+ | `include T::Props::Constructor` | 🛠 | Shimmed |
112
+ | `sig` | ✅ | Removed |
113
+ | `T.absurd(foo)` | ✅ | Shimmed |
114
+ | `T.assert_type!(foo, bar)` | ✅ | `foo` |
115
+ | `T.bind(self, foo)` | ✅ | `self` |
116
+ | `T.cast(foo, bar)` | ✅ | `foo` |
117
+ | `T.let(foo, bar)` | ✅ | `foo` |
118
+ | `T.must(foo)` | ✅ | `foo` |
119
+ | `T.reveal_type(foo)` | ✅ | `foo` |
120
+ | `T.type_alias { foo }` | ✅ | Shimmed |
121
+ | `T.unsafe(foo)` | ✅ | `foo` |
71
122
 
72
123
  In the above table, for `Status`:
73
124
 
@@ -70,7 +70,7 @@ module Sorbet
70
70
  class ParsingError < StandardError
71
71
  end
72
72
 
73
- attr_reader :source, :line_counts, :errors, :patterns
73
+ attr_reader :source, :line_counts, :errors, :patterns, :heredocs
74
74
 
75
75
  def initialize(source)
76
76
  super(source)
@@ -86,11 +86,12 @@ module Sorbet
86
86
  @line_counts << MultiByteString.new(last_index, line)
87
87
  end
88
88
 
89
- last_index += line.size
89
+ last_index += line.bytesize
90
90
  end
91
91
 
92
92
  @errors = []
93
93
  @patterns = []
94
+ @heredocs = []
94
95
  end
95
96
 
96
97
  def self.erase(source)
@@ -133,15 +134,6 @@ module Sorbet
133
134
  end
134
135
  end
135
136
 
136
- # Loop through all of the scanner events and define a basic method that
137
- # wraps everything into a node class.
138
- SCANNER_EVENTS.each do |event|
139
- define_method(:"on_#{event}") do |value|
140
- range = loc.then { |start| start...(start + (value&.size || 0)) }
141
- Node.new(:"@#{event}", [value], range)
142
- end
143
- end
144
-
145
137
  # Better location information for aref.
146
138
  def on_aref(recv, arg)
147
139
  rend = arg.range.end + source[arg.range.end..].index("]") + 1
@@ -150,23 +142,90 @@ module Sorbet
150
142
 
151
143
  # Better location information for arg_paren.
152
144
  def on_arg_paren(arg)
153
- rbegin = source[..arg.range.begin].rindex("(")
154
- rend = arg.range.end + source[arg.range.end..].index(")") + 1
155
- Node.new(:arg_paren, [arg], rbegin...rend)
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
156
171
  end
157
172
 
158
173
  # Better location information for brace_block.
159
174
  def on_brace_block(params, body)
160
- rbegin = source[...(params || body).range.begin].rindex("{")
161
- rend = body.range.end + source[body.range.end..].index("}") + 1
162
- Node.new(:brace_block, [params, body], rbegin...rend)
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
163
186
  end
164
187
 
165
188
  # Better location information for do_block.
166
189
  def on_do_block(params, body)
167
- rbegin = source[...(params || body).range.begin].rindex("do")
168
- rend = body.range.end + source[body.range.end..].index("end") + 3
169
- Node.new(:do_block, [params, body], rbegin...rend)
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.bytesize) }
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.bytesize) }
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)
170
229
  end
171
230
 
172
231
  # Track the parsing errors for nicer error messages.
@@ -174,12 +233,33 @@ module Sorbet
174
233
  errors << "line #{lineno}: #{error}"
175
234
  end
176
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
+
248
+ # Loop through all of the scanner events and define a basic method that
249
+ # wraps everything into a node class.
250
+ SCANNER_EVENTS.each do |event|
251
+ next if handled.include?(:"on_#{event}")
252
+
253
+ define_method(:"on_#{event}") do |value|
254
+ range = loc.then { |start| start...(start + (value&.bytesize || 0)) }
255
+ Node.new(:"@#{event}", [value], range)
256
+ end
257
+ end
258
+
177
259
  # Loop through the parser events and generate a method for each event. If
178
260
  # it's one of the _new methods, then use arrays like SexpBuilderPP. If
179
261
  # it's an _add method then just append to the array. If it's a normal
180
262
  # method, then create a new node and determine its bounds.
181
- handled = private_instance_methods(false)
182
-
183
263
  PARSER_EVENT_TABLE.each do |event, arity|
184
264
  next if handled.include?(:"on_#{event}")
185
265
 
@@ -194,8 +274,10 @@ module Sorbet
194
274
  range =
195
275
  if node.body.empty?
196
276
  value.range
197
- else
277
+ elsif node.range && value.range
198
278
  (node.range.begin...value.range.end)
279
+ else
280
+ node.range || value.range
199
281
  end
200
282
 
201
283
  node.class.new(node.event, node.body + [value], range)
@@ -100,13 +100,37 @@ module Sorbet
100
100
  end
101
101
  end
102
102
 
103
+ # typed: ignore
104
+ # typed: false
105
+ # typed: true
106
+ # typed: strong
107
+ class TypedCommentPattern < Pattern
108
+ def replace(segment)
109
+ segment.gsub(/(\A#\s*typed:\s*)(?:ignore|false|true|strong)(\s*)\z/) do
110
+ "#{$1}ignore#{$2}"
111
+ end
112
+ end
113
+ end
114
+
115
+ def on_comment(comment)
116
+ super.tap do |node|
117
+ if lineno == 1 && comment.match?(/\A#\s*typed:\s*(?:ignore|false|true|strong)\s*\z/)
118
+ # typed: ignore
119
+ # typed: false
120
+ # typed: true
121
+ # typed: strong
122
+ patterns << TypedCommentPattern.new(node.range)
123
+ end
124
+ end
125
+ end
126
+
103
127
  def on_method_add_arg(call, arg_paren)
104
- if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident (?:must|reveal_type|unsafe)>>/) && arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
128
+ 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
129
  # T.must(foo)
106
130
  # T.reveal_type(foo)
107
131
  # T.unsafe(foo)
108
132
  patterns << TOneArgMethodCallParensPattern.new(call.range.begin...arg_paren.range.end)
109
- elsif call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>\z/) && arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
133
+ 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
134
  # T.assert_type!(foo, bar)
111
135
  # T.cast(foo, bar)
112
136
  # T.let(foo, bar)
@@ -114,18 +138,18 @@ module Sorbet
114
138
  call.range.begin...arg_paren.range.end,
115
139
  comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
116
140
  )
117
- elsif call.match?(/<call <var_ref <@const T>> <@period \.> <@ident bind>>/) && arg_paren.match?(/<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>/)
141
+ 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
142
  # T.bind(self, foo)
119
143
  patterns << TTwoArgMethodCallParensPattern.new(
120
144
  call.range.begin...arg_paren.range.end,
121
145
  comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
122
146
  )
123
- elsif call.match?(/<fcall <@ident (?:abstract|final|interface)!>>/) && arg_paren.match?("<args >")
147
+ elsif call.match?(/\A<fcall <@ident (?:abstract|final|interface)!>>\z/) && arg_paren.match?("<args >")
124
148
  # abstract!
125
149
  # final!
126
150
  # interface!
127
- patterns << DeclarationPattern.new(call.range.begin...arg_paren.range.end)
128
- elsif call.match?("<fcall <@ident mixes_in_class_methods>>") && arg_paren.match?(/<arg_paren <args_add_block <args <.+>>> false>>/)
151
+ patterns << DeclarationPattern.new(call.range)
152
+ elsif call.match?("<fcall <@ident mixes_in_class_methods>>") && arg_paren.match?(/\A<arg_paren <args_add_block <args <.+>>> false>>\z/)
129
153
  # mixes_in_class_methods(foo)
130
154
  patterns << MixesInClassMethodsPattern.new(call.range.begin...arg_paren.range.end)
131
155
  end
@@ -159,8 +183,8 @@ module Sorbet
159
183
  end
160
184
 
161
185
  def on_command(ident, args_add_block)
162
- if ident.match?(/<@ident (?:const|prop)>/)
163
- if args_add_block.match?(/<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> <bare_assoc_hash .+> false>/)
186
+ if ident.match?(/\A<@ident (?:const|prop)>\z/)
187
+ if args_add_block.match?(/\A<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> <bare_assoc_hash .+> false>\z/)
164
188
  # prop :foo, String, default: ""
165
189
  # const :foo, String, default: ""
166
190
  patterns << PropWithOptionsPattern.new(
@@ -168,7 +192,7 @@ module Sorbet
168
192
  first_comma: args_add_block.body[0].body[0].range.end - ident.range.begin,
169
193
  second_comma: args_add_block.body[0].body[1].range.end - ident.range.begin
170
194
  )
171
- elsif args_add_block.match?(/<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> false>/)
195
+ elsif args_add_block.match?(/\A<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> false>\z/)
172
196
  # prop :foo, String
173
197
  # const :foo, String
174
198
  patterns << PropWithoutOptionsPattern.new(
@@ -182,7 +206,7 @@ module Sorbet
182
206
  end
183
207
 
184
208
  def on_command_call(var_ref, period, ident, args_add_block)
185
- if var_ref.match?("<var_ref <@const T>>") && period.match?("<@period .>") && ident.match?(/<@ident (?:must|reveal_type|unsafe)>/) && args_add_block.match?(/<args_add_block <args <.+>> false>/) && args_add_block.body[0].body.length == 1
209
+ 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
186
210
  # T.must foo
187
211
  # T.reveal_type foo
188
212
  # T.unsafe foo
@@ -211,10 +235,10 @@ module Sorbet
211
235
  end
212
236
 
213
237
  def on_stmts_add(node, value)
214
- if value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>/)
238
+ if value.match?(/\A<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>\z/)
215
239
  # sig { foo }
216
240
  patterns << SigBracesPattern.new(value.range)
217
- elsif value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <do_block <bodystmt .+>>>/)
241
+ elsif value.match?(/\A<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <do_block <bodystmt .+>>>\z/)
218
242
  # sig do foo end
219
243
  patterns << SigBlockPattern.new(value.range)
220
244
  end
@@ -41,13 +41,7 @@ module T
41
41
  # class level and set appropriate values.
42
42
  def initialize(hash = {})
43
43
  self.class.props.each do |name, rules|
44
- if hash.key?(name)
45
- instance_variable_set("@#{name}", hash.delete(name))
46
- elsif rules.key?(:default)
47
- instance_variable_set("@#{name}", rules[:default])
48
- else
49
- raise ArgumentError, "missing keyword: #{name}"
50
- end
44
+ instance_variable_set("@#{name}", hash.key?(name) ? hash.delete(name) : rules[:default])
51
45
  end
52
46
 
53
47
  raise ArgumentError, "unknown keyword: #{hash.keys.first}" unless hash.empty?
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sorbet/eraser/t/enum"
4
+ require "sorbet/eraser/t/props"
5
+ require "sorbet/eraser/t/struct"
6
+
7
+ # For some constructs, it doesn't make as much sense to entirely remove them
8
+ # since they're actually used to change runtime behavior. For example, T.absurd
9
+ # will always raise an error. In this case instead of removing the content, we
10
+ # can just shim it.
11
+ module T
12
+ # These methods should really not be being called in a loop or any other kind
13
+ # of hot path, so here we're just going to shim them.
14
+ module Helpers
15
+ def abstract!; end
16
+ def interface!; end
17
+ def final!; end
18
+ def sealed!; end
19
+ def mixes_in_class_methods(*); end
20
+ def requires_ancestor(*); end
21
+ end
22
+
23
+ # Similar to the Helpers module, these things should only be called a couple
24
+ # of times, so shimming them here.
25
+ module Generic
26
+ include Helpers
27
+ def type_member(*, **); end
28
+ def type_template(*, **); end
29
+ end
30
+
31
+ # Keeping this module as a thing so that if there's any kind of weird
32
+ # reflection going on like is_a?(T::Sig) it will still work.
33
+ module Sig
34
+ end
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
+
64
+ # Type aliases don't actually do anything, but they are usually assigned to
65
+ # constants, so in that case we need to return something.
66
+ def self.type_alias
67
+ Object.new
68
+ end
69
+
70
+ # Absurd always raises a TypeError within Sorbet, so mirroring that behavior
71
+ # here when T.absurd is called.
72
+ def self.absurd(value)
73
+ raise TypeError, value
74
+ end
75
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sorbet
4
4
  module Eraser
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
data/lib/t.rb CHANGED
@@ -1,47 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "t/enum"
4
- require "t/props"
5
- require "t/struct"
6
-
7
- # For some constructs, it doesn't make as much sense to entirely remove them
8
- # since they're actually used to change runtime behavior. For example, T.absurd
9
- # will always raise an error. In this case instead of removing the content, we
10
- # can just shim it.
11
- module T
12
- # These methods should really not be being called in a loop or any other kind
13
- # of hot path, so here we're just going to shim them.
14
- module Helpers
15
- def abstract!; end
16
- def interface!; end
17
- def final!; end
18
- def sealed!; end
19
- def mixes_in_class_methods(*); end
20
- def requires_ancestor(*); end
21
- end
22
-
23
- # Similar to the Helpers module, these things should only be called a couple
24
- # of times, so shimming them here.
25
- module Generic
26
- include Helpers
27
- def type_member(*, **); end
28
- def type_template(*, **); end
29
- end
30
-
31
- # Keeping this module as a thing so that if there's any kind of weird
32
- # reflection going on like is_a?(T::Sig) it will still work.
33
- module Sig
34
- end
35
-
36
- # Type aliases don't actually do anything, but they are usually assigned to
37
- # constants, so in that case we need to return something.
38
- def self.type_alias
39
- Object.new
40
- end
41
-
42
- # Absurd always raises a TypeError within Sorbet, so mirroring that behavior
43
- # here when T.absurd is called.
44
- def self.absurd(value)
45
- raise TypeError, value
46
- end
47
- end
3
+ require "sorbet/eraser/t"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorbet-eraser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-06-27 00:00:00.000000000 Z
11
+ date: 2023-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -77,18 +77,19 @@ files:
77
77
  - lib/sorbet/eraser/cli.rb
78
78
  - lib/sorbet/eraser/parser.rb
79
79
  - lib/sorbet/eraser/patterns.rb
80
+ - lib/sorbet/eraser/t.rb
81
+ - lib/sorbet/eraser/t/enum.rb
82
+ - lib/sorbet/eraser/t/props.rb
83
+ - lib/sorbet/eraser/t/struct.rb
80
84
  - lib/sorbet/eraser/version.rb
81
85
  - lib/t.rb
82
- - lib/t/enum.rb
83
- - lib/t/props.rb
84
- - lib/t/struct.rb
85
86
  - sorbet-eraser.gemspec
86
87
  homepage: https://github.com/kddnewton/sorbet-eraser
87
88
  licenses:
88
89
  - MIT
89
90
  metadata:
90
91
  bug_tracker_uri: https://github.com/kddnewton/sorbet-eraser/issues
91
- changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.3.0/CHANGELOG.md
92
+ changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.4.0/CHANGELOG.md
92
93
  source_code_uri: https://github.com/kddnewton/sorbet-eraser
93
94
  rubygems_mfa_required: 'true'
94
95
  post_install_message:
File without changes
File without changes