sorbet-eraser 0.2.0 → 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/CHANGELOG.md +15 -1
- data/Gemfile.lock +1 -1
- data/README.md +2 -0
- data/lib/sorbet/eraser/parser.rb +66 -17
- data/lib/sorbet/eraser/patterns.rb +74 -55
- data/lib/sorbet/eraser/version.rb +1 -1
- data/lib/t/props.rb +17 -11
- 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: 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/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
data/README.md
CHANGED
@@ -39,6 +39,8 @@ bundle exec sorbet-eraser '**/*.rb'
|
|
39
39
|
|
40
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
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"`.
|
43
|
+
|
42
44
|
### Status
|
43
45
|
|
44
46
|
Below is a table of the status of each `sorbet-runtime` construct and its current support status.
|
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
|
@@ -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?(/<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)
|
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
|
-
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 >")
|
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!
|
139
127
|
patterns << DeclarationPattern.new(call.range.begin...arg_paren.range.end)
|
140
|
-
|
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>>/)
|
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)
|
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
162
|
if ident.match?(/<@ident (?:const|prop)>/)
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
patterns <<
|
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
|
+
)
|
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?(/<@ident (?:must|reveal_type|unsafe)>/) && args_add_block.match?(/<args_add_block <args <.+>> false>/) && 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
|
-
# sig { foo }
|
199
214
|
if value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>/)
|
215
|
+
# sig { foo }
|
200
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)
|
201
220
|
end
|
202
221
|
|
203
222
|
super
|
data/lib/t/props.rb
CHANGED
@@ -20,31 +20,37 @@ 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
|
-
hash.
|
45
|
-
|
46
|
-
|
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
|
47
51
|
end
|
52
|
+
|
53
|
+
raise ArgumentError, "unknown keyword: #{hash.keys.first}" unless hash.empty?
|
48
54
|
end
|
49
55
|
|
50
56
|
# This module is entirely empty because we haven't implemented anything from
|
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.0
|
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.0/CHANGELOG.md
|
92
92
|
source_code_uri: https://github.com/kddnewton/sorbet-eraser
|
93
93
|
rubygems_mfa_required: 'true'
|
94
94
|
post_install_message:
|