sorbet-eraser 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/CHANGELOG.md +15 -1
- data/Gemfile.lock +5 -2
- data/README.md +18 -6
- data/lib/sorbet/eraser/parser.rb +66 -17
- data/lib/sorbet/eraser/patterns.rb +92 -51
- data/lib/sorbet/eraser/version.rb +1 -1
- data/lib/t/enum.rb +1 -1
- data/lib/t/props.rb +65 -0
- data/lib/t/struct.rb +44 -4
- data/lib/t.rb +1 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ddbc396f3f164a3d0e67e9cf7046733ce52f792eb0abc17e9f5f056f415cd5e
|
4
|
+
data.tar.gz: 965ef9955b75bba0a2702ea127d5923d7e4b1a55ed2b2e6dca239970adbb0fe1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5deb006431e8e31ed089fe6e8fba281f3c65487fc6996a853b05bb0d9600ff034232e7319fb44912a69dc89b60b160900b9b503a17991d19da2bf02f5803c5f8
|
7
|
+
data.tar.gz: a6a20f8aa21b2a2c9e75a4c7e83db5b28556516296d16f55eedd0113da807cbf0c3a827fbeda7eaf9fb95d2a29a9f0c4ddc9090d638e00469989520d2c492f1d
|
data/.github/workflows/main.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,11 +6,25 @@ 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.0] - 2023-06-27
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Support for the `default` and `without_accessors` options for `T::Struct`.
|
14
|
+
|
15
|
+
## [0.2.0] - 2023-06-26
|
16
|
+
|
17
|
+
### Added
|
18
|
+
|
19
|
+
- Better support for `T::Struct` subclasses.
|
20
|
+
|
9
21
|
## [0.1.1] - 2021-11-17
|
10
22
|
|
11
23
|
### Changed
|
12
24
|
|
13
25
|
- Require MFA for releasing.
|
14
26
|
|
15
|
-
[unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.
|
27
|
+
[unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.0...HEAD
|
28
|
+
[0.3.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.2.0...v0.3.0
|
29
|
+
[0.2.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.1.1...v0.2.0
|
16
30
|
[0.1.1]: https://github.com/kddnewton/sorbet-eraser/compare/f6a712...v0.1.1
|
data/Gemfile.lock
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sorbet-eraser (0.
|
4
|
+
sorbet-eraser (0.3.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
minitest (5.
|
9
|
+
minitest (5.18.1)
|
10
10
|
rake (13.0.6)
|
11
11
|
|
12
12
|
PLATFORMS
|
13
|
+
arm64-darwin-22
|
13
14
|
x86_64-darwin-19
|
15
|
+
x86_64-darwin-21
|
16
|
+
x86_64-linux
|
14
17
|
|
15
18
|
DEPENDENCIES
|
16
19
|
bundler
|
data/README.md
CHANGED
@@ -27,17 +27,19 @@ Or install it yourself as:
|
|
27
27
|
|
28
28
|
## Usage
|
29
29
|
|
30
|
-
|
30
|
+
There are two ways to use this gem, depending on your needs.
|
31
31
|
|
32
|
-
|
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.
|
33
33
|
|
34
|
-
|
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:
|
35
35
|
|
36
|
-
```
|
36
|
+
```bash
|
37
37
|
bundle exec sorbet-eraser '**/*.rb'
|
38
38
|
```
|
39
39
|
|
40
|
-
It accepts any number of filepaths/patterns on the command line and will modify the source files with their erased contents.
|
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.
|
41
|
+
|
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"`.
|
41
43
|
|
42
44
|
### Status
|
43
45
|
|
@@ -50,7 +52,12 @@ Below is a table of the status of each `sorbet-runtime` construct and its curren
|
|
50
52
|
| `mixes_in_class_methods(*)`, `requires_ancestor(*)` | ✅ | Shimmed |
|
51
53
|
| `type_member(*)`, `type_template(*)` | ✅ | Shimmed |
|
52
54
|
| `class Foo < T::Enum` | ✅ | Shimmed |
|
55
|
+
| `class Foo < T::InexactStruct` | 🛠 | Shimmed |
|
53
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 |
|
54
61
|
| `sig` | ✅ | Removed |
|
55
62
|
| `T.absurd(foo)` | ✅ | Shimmed |
|
56
63
|
| `T.assert_type!(foo, bar)` | ✅ | `foo` |
|
@@ -62,7 +69,12 @@ Below is a table of the status of each `sorbet-runtime` construct and its curren
|
|
62
69
|
| `T.type_alias { foo }` | ✅ | Shimmed |
|
63
70
|
| `T.unsafe(foo)` | ✅ | `foo` |
|
64
71
|
|
65
|
-
In the above table
|
72
|
+
In the above table, for `Status`:
|
73
|
+
|
74
|
+
* ✅ means that we are confident this is replaced 1:1.
|
75
|
+
* 🛠 means there may be APIs that are not entirely supported. If you run into something that is missing, please open an issue.
|
76
|
+
|
77
|
+
In the above table, for `Replacement`:
|
66
78
|
|
67
79
|
* `Shimmed` means that this gem provides a replacement module that will simply do nothing when its respective methods are called. We do this in order to maintain the same interface in the case that someone is doing runtime reflection. Also because anything that is shimmed will not be called that much/will not be in a hot path so performance is not really a consideration for those cases.
|
68
80
|
* `Removed` means that the construct is removed entirely from the source.
|
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
|
|
@@ -108,25 +111,83 @@ module Sorbet
|
|
108
111
|
line_counts[lineno - 1][column]
|
109
112
|
end
|
110
113
|
|
114
|
+
def find_loc(args)
|
115
|
+
ranges = []
|
116
|
+
|
117
|
+
args.each do |arg|
|
118
|
+
case arg
|
119
|
+
when Node
|
120
|
+
ranges << arg.range if arg.range
|
121
|
+
when Array
|
122
|
+
ranges << find_loc(arg)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
case ranges.length
|
127
|
+
when 0
|
128
|
+
nil
|
129
|
+
when 1
|
130
|
+
ranges.first
|
131
|
+
else
|
132
|
+
ranges.first.begin...ranges.last.end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
111
136
|
# Loop through all of the scanner events and define a basic method that
|
112
137
|
# wraps everything into a node class.
|
113
138
|
SCANNER_EVENTS.each do |event|
|
114
139
|
define_method(:"on_#{event}") do |value|
|
115
|
-
range = loc.then { |start| start
|
140
|
+
range = loc.then { |start| start...(start + (value&.size || 0)) }
|
116
141
|
Node.new(:"@#{event}", [value], range)
|
117
142
|
end
|
118
143
|
end
|
119
144
|
|
145
|
+
# Better location information for aref.
|
146
|
+
def on_aref(recv, arg)
|
147
|
+
rend = arg.range.end + source[arg.range.end..].index("]") + 1
|
148
|
+
Node.new(:aref, [recv, arg], recv.range.begin...rend)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Better location information for arg_paren.
|
152
|
+
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)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Better location information for brace_block.
|
159
|
+
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)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Better location information for do_block.
|
166
|
+
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)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Track the parsing errors for nicer error messages.
|
173
|
+
def on_parse_error(error)
|
174
|
+
errors << "line #{lineno}: #{error}"
|
175
|
+
end
|
176
|
+
|
120
177
|
# Loop through the parser events and generate a method for each event. If
|
121
178
|
# it's one of the _new methods, then use arrays like SexpBuilderPP. If
|
122
179
|
# it's an _add method then just append to the array. If it's a normal
|
123
180
|
# method, then create a new node and determine its bounds.
|
181
|
+
handled = private_instance_methods(false)
|
182
|
+
|
124
183
|
PARSER_EVENT_TABLE.each do |event, arity|
|
184
|
+
next if handled.include?(:"on_#{event}")
|
185
|
+
|
125
186
|
if event =~ /\A(.+)_new\z/ && event != :assoc_new
|
126
187
|
prefix = $1.to_sym
|
127
188
|
|
128
189
|
define_method(:"on_#{event}") do
|
129
|
-
Node.new(prefix, [],
|
190
|
+
Node.new(prefix, [], nil)
|
130
191
|
end
|
131
192
|
elsif event =~ /_add\z/
|
132
193
|
define_method(:"on_#{event}") do |node, value|
|
@@ -134,29 +195,17 @@ module Sorbet
|
|
134
195
|
if node.body.empty?
|
135
196
|
value.range
|
136
197
|
else
|
137
|
-
(node.range.begin
|
198
|
+
(node.range.begin...value.range.end)
|
138
199
|
end
|
139
200
|
|
140
201
|
node.class.new(node.event, node.body + [value], range)
|
141
202
|
end
|
142
|
-
elsif event == :parse_error
|
143
|
-
# skip this, as we're going to define it below
|
144
203
|
else
|
145
204
|
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)
|
205
|
+
Node.new(event, args, find_loc(args))
|
152
206
|
end
|
153
207
|
end
|
154
208
|
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
209
|
end
|
161
210
|
end
|
162
211
|
end
|
@@ -89,75 +89,104 @@ module Sorbet
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
# T.must foo => foo
|
93
|
+
# T.reveal_type foo => foo
|
94
|
+
# T.unsafe foo => foo
|
95
|
+
class TMustNoParensPattern < Pattern
|
96
|
+
def replace(segment)
|
97
|
+
segment.gsub(/(T\s*\.(?:must|reveal_type|unsafe)\s*)(.+)/) do
|
98
|
+
"#{blank($1)}#{$2}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
92
103
|
def on_method_add_arg(call, arg_paren)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
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>>/)
|
105
|
+
# T.must(foo)
|
106
|
+
# T.reveal_type(foo)
|
107
|
+
# T.unsafe(foo)
|
98
108
|
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>>/)
|
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?(/<call <var_ref <@const T>> <@period \.> <@ident bind>>/) && arg_paren.match?(/<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>/)
|
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?(/<fcall <@ident (?:abstract|final|interface)!>>/) && arg_paren.match?("<args >")
|
124
|
+
# abstract!
|
125
|
+
# final!
|
126
|
+
# 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>>/)
|
129
|
+
# mixes_in_class_methods(foo)
|
130
|
+
patterns << MixesInClassMethodsPattern.new(call.range.begin...arg_paren.range.end)
|
99
131
|
end
|
100
132
|
|
101
|
-
|
102
|
-
|
103
|
-
# T.let(foo, bar)
|
104
|
-
if call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>\z/) &&
|
105
|
-
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
106
|
-
patterns <<
|
107
|
-
TTwoArgMethodCallParensPattern.new(
|
108
|
-
call.range.begin...arg_paren.range.end,
|
109
|
-
comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
|
110
|
-
)
|
111
|
-
end
|
133
|
+
super
|
134
|
+
end
|
112
135
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
136
|
+
# prop :foo, String => prop :foo
|
137
|
+
# const :foo, String => const :foo
|
138
|
+
class PropWithoutOptionsPattern < Pattern
|
139
|
+
def replace(segment)
|
140
|
+
segment.dup.tap do |replacement|
|
141
|
+
range = metadata.fetch(:comma)..-1
|
142
|
+
replacement[range] = blank(replacement[range])
|
143
|
+
end
|
121
144
|
end
|
145
|
+
end
|
122
146
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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)
|
130
154
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
patterns << MixesInClassMethodsPattern.new(call.range.begin...arg_paren.range.end)
|
155
|
+
range = (first_comma + 1)..second_comma
|
156
|
+
replacement[range] = blank(replacement[range])
|
157
|
+
end
|
135
158
|
end
|
136
|
-
|
137
|
-
super
|
138
159
|
end
|
139
160
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
161
|
+
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>/)
|
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?(/<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> false>/)
|
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
|
+
)
|
147
178
|
end
|
148
179
|
end
|
180
|
+
|
181
|
+
super
|
149
182
|
end
|
150
183
|
|
151
184
|
def on_command_call(var_ref, period, ident, args_add_block)
|
152
|
-
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?(/<@ident (?:must|reveal_type|unsafe)>/) && args_add_block.match?(/<args_add_block <args <.+>> false>/) && args_add_block.body[0].body.length == 1
|
153
186
|
# T.must foo
|
154
187
|
# T.reveal_type foo
|
155
188
|
# T.unsafe foo
|
156
|
-
|
157
|
-
args_add_block.match?(/<args_add_block <args <.+>> false>/) &&
|
158
|
-
args_add_block.body[0].body.length == 1
|
159
|
-
patterns << TMustNoParensPattern.new(var_ref.range.begin...args_add_block.range.end)
|
160
|
-
end
|
189
|
+
patterns << TMustNoParensPattern.new(var_ref.range.begin..args_add_block.range.end)
|
161
190
|
end
|
162
191
|
|
163
192
|
super
|
@@ -172,10 +201,22 @@ module Sorbet
|
|
172
201
|
end
|
173
202
|
end
|
174
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
|
+
|
175
213
|
def on_stmts_add(node, value)
|
176
|
-
# sig { foo }
|
177
214
|
if value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>/)
|
215
|
+
# sig { foo }
|
178
216
|
patterns << SigBracesPattern.new(value.range)
|
217
|
+
elsif value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <do_block <bodystmt .+>>>/)
|
218
|
+
# sig do foo end
|
219
|
+
patterns << SigBlockPattern.new(value.range)
|
179
220
|
end
|
180
221
|
|
181
222
|
super
|
data/lib/t/enum.rb
CHANGED
@@ -226,7 +226,7 @@ module T
|
|
226
226
|
@values.freeze
|
227
227
|
@mapping.freeze
|
228
228
|
|
229
|
-
orphaned_instances =
|
229
|
+
orphaned_instances = @values - @mapping.values
|
230
230
|
if !orphaned_instances.empty?
|
231
231
|
raise "Enum values must be assigned to constants: #{orphaned_instances.map {|v| v.instance_variable_get('@serialized_val')}}"
|
232
232
|
end
|
data/lib/t/props.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module T
|
4
|
+
# Here is the place we don't match up to sorbet because we simply don't
|
5
|
+
# implement as much as they do in the runtime. If there are pieces of this
|
6
|
+
# that folks would like implemented I'd be happy to include them. This is here
|
7
|
+
# just to get a baseline for folks using T::Struct with basic const/prop
|
8
|
+
# calls.
|
9
|
+
module Props
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Here we're implementing a very basic version of the prop/const methods
|
15
|
+
# that are in sorbet-runtime. These are only here to allow consumers to call
|
16
|
+
# them and not raise errors and for bookkeeping.
|
17
|
+
module ClassMethods
|
18
|
+
def props
|
19
|
+
@props ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
def prop(name, rules = {})
|
23
|
+
create_prop(name, rules)
|
24
|
+
attr_accessor(name) unless rules[:without_accessors]
|
25
|
+
end
|
26
|
+
|
27
|
+
def const(name, rules = {})
|
28
|
+
create_prop(name, rules)
|
29
|
+
attr_reader(name) unless rules[:without_accessors]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def create_prop(name, rules)
|
35
|
+
props << [name, rules]
|
36
|
+
props.sort_by!(&:first)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Here we're going to check against the props that have been defined on the
|
41
|
+
# class level and set appropriate values.
|
42
|
+
def initialize(hash = {})
|
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
|
51
|
+
end
|
52
|
+
|
53
|
+
raise ArgumentError, "unknown keyword: #{hash.keys.first}" unless hash.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
# This module is entirely empty because we haven't implemented anything from
|
57
|
+
# sorbet-runtime here.
|
58
|
+
module Serializable
|
59
|
+
end
|
60
|
+
|
61
|
+
# This is empty for the same reason.
|
62
|
+
module Constructor
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/t/struct.rb
CHANGED
@@ -1,12 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module T
|
4
|
+
# This is the actual parent class of T::Struct. It's here to match up the
|
5
|
+
# inheritance chain in case someone is doing reflection.
|
6
|
+
class InexactStruct
|
7
|
+
include Props
|
8
|
+
include Props::Serializable
|
9
|
+
include Props::Constructor
|
10
|
+
end
|
11
|
+
|
4
12
|
# This is a shim for the T::Struct class because since you're actually
|
5
13
|
# inheriting from the class there's not really a way to remove it from the
|
6
14
|
# source.
|
7
|
-
class Struct
|
8
|
-
|
9
|
-
|
10
|
-
|
15
|
+
class Struct < InexactStruct
|
16
|
+
def self.inherited(child)
|
17
|
+
super(child)
|
18
|
+
|
19
|
+
child.define_singleton_method(:inherited) do |grandchild|
|
20
|
+
super(grandchild)
|
21
|
+
raise "#{grandchild.name} is a subclass of T::Struct and cannot be subclassed"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ImmutableStruct < InexactStruct
|
27
|
+
def self.inherited(child)
|
28
|
+
super(child)
|
29
|
+
|
30
|
+
child.define_singleton_method(:inherited) do |grandchild|
|
31
|
+
super(grandchild)
|
32
|
+
raise "#{grandchild.name} is a subclass of T::ImmutableStruct and cannot be subclassed"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(hash = {})
|
37
|
+
super
|
38
|
+
freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
# Matches the signature in Props, but raises since this is an immutable struct and only const is allowed
|
42
|
+
def self.prop(name, rules = {})
|
43
|
+
return super if rules[:immutable]
|
44
|
+
|
45
|
+
raise "Cannot use `prop` in #{self.name} because it is an immutable struct. Use `const` instead"
|
46
|
+
end
|
47
|
+
|
48
|
+
def with(changed_props)
|
49
|
+
raise "Cannot use `with` in #{self.class.name} because it is an immutable struct"
|
50
|
+
end
|
11
51
|
end
|
12
52
|
end
|
data/lib/t.rb
CHANGED
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.
|
4
|
+
version: 0.3.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:
|
11
|
+
date: 2023-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- lib/sorbet/eraser/version.rb
|
81
81
|
- lib/t.rb
|
82
82
|
- lib/t/enum.rb
|
83
|
+
- lib/t/props.rb
|
83
84
|
- lib/t/struct.rb
|
84
85
|
- sorbet-eraser.gemspec
|
85
86
|
homepage: https://github.com/kddnewton/sorbet-eraser
|
@@ -87,7 +88,7 @@ licenses:
|
|
87
88
|
- MIT
|
88
89
|
metadata:
|
89
90
|
bug_tracker_uri: https://github.com/kddnewton/sorbet-eraser/issues
|
90
|
-
changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.
|
91
|
+
changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.3.0/CHANGELOG.md
|
91
92
|
source_code_uri: https://github.com/kddnewton/sorbet-eraser
|
92
93
|
rubygems_mfa_required: 'true'
|
93
94
|
post_install_message:
|
@@ -105,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
106
|
- !ruby/object:Gem::Version
|
106
107
|
version: '0'
|
107
108
|
requirements: []
|
108
|
-
rubygems_version: 3.
|
109
|
+
rubygems_version: 3.4.1
|
109
110
|
signing_key:
|
110
111
|
specification_version: 4
|
111
112
|
summary: Erase all traces of sorbet-runtime code.
|