sorbet-eraser 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +11 -1
- data/Gemfile.lock +1 -1
- data/README.md +25 -25
- data/lib/sorbet/eraser/autoload.rb +15 -14
- data/lib/sorbet/eraser/cli.rb +22 -5
- data/lib/sorbet/eraser/parser.rb +15 -15
- data/lib/sorbet/eraser/patterns.rb +16 -17
- data/lib/sorbet/eraser/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 420576226d037471f75759599ea53397cd251dd546431b24e9d3cf556285902a
|
4
|
+
data.tar.gz: 7c6d09a382019245254f55d0a9eff1b4cd2430d066c364efde59567ada2f56c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5478636318a06fe00d279813bd724659d6653efef7364ea583e11566e455cf9528e8c14338dfdab11298e329225ca9ef2c0847df4125b0edea09f4ca5a953452
|
7
|
+
data.tar.gz: 26d79c3b183ef5f7a629205b94e61a09862db83ca50884d68e1e7d22954b1543355787e7fd798223ff8b483bdbdcbb5c14a49959d72f19a12bfcedd13f8f51e8
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,15 @@ 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.5.0] - 2023-07-13
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Replace `typed: strict` comments with empty comments.
|
14
|
+
- Replace all `typed:` sigil comments with empty comments instead of `typed: ignore`. Do this because `typed: ignore` longer than other options, which can cause issues with byte ranges, and violates an assumption by this gem that it is only erasing, not adding content.
|
15
|
+
- Add a `--verify` option to the CLI to ensure output is valid Ruby.
|
16
|
+
- Enhance `sorbet/eraser/autoload` to hook into `load_iseq` even if bootsnap is not present.
|
17
|
+
|
9
18
|
## [0.4.0] - 2023-07-03
|
10
19
|
|
11
20
|
### Added
|
@@ -41,7 +50,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
41
50
|
|
42
51
|
- Require MFA for releasing.
|
43
52
|
|
44
|
-
[unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.4.
|
53
|
+
[unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.4.1...HEAD
|
54
|
+
[0.4.1]: https://github.com/kddnewton/sorbet-eraser/compare/v0.4.0...v0.4.1
|
45
55
|
[0.4.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.1...v0.4.0
|
46
56
|
[0.3.1]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.0...v0.3.1
|
47
57
|
[0.3.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.2.0...v0.3.0
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -27,7 +27,7 @@ end
|
|
27
27
|
will be transformed into
|
28
28
|
|
29
29
|
```ruby
|
30
|
-
|
30
|
+
#
|
31
31
|
|
32
32
|
class HelloWorld
|
33
33
|
|
@@ -95,30 +95,30 @@ If you used any runtime structures like `T::Struct` or `T::Enum` you'll need a r
|
|
95
95
|
|
96
96
|
Below is a table of the status of each `sorbet-runtime` construct and its current support status.
|
97
97
|
|
98
|
-
| Construct | Status | Replacement
|
99
|
-
| --------------------------------------------------- | ------ |
|
100
|
-
| `# typed: foo` | ✅
|
101
|
-
| `extend T::*` | ✅
|
102
|
-
| `abstract!`, `final!`, `interface!`, `sealed!` | ✅
|
103
|
-
| `mixes_in_class_methods(*)`, `requires_ancestor(*)` | ✅
|
104
|
-
| `type_member(*)`, `type_template(*)` | ✅
|
105
|
-
| `class Foo < T::Enum` | ✅
|
106
|
-
| `class Foo < T::InexactStruct` | 🛠
|
107
|
-
| `class Foo < T::Struct` | 🛠
|
108
|
-
| `class Foo < T::ImmutableStruct` | 🛠
|
109
|
-
| `include T::Props` | 🛠
|
110
|
-
| `include T::Props::Serializable` | 🛠
|
111
|
-
| `include T::Props::Constructor` | 🛠
|
112
|
-
| `sig` | ✅
|
113
|
-
| `T.absurd(foo)` | ✅
|
114
|
-
| `T.assert_type!(foo, bar)` | ✅
|
115
|
-
| `T.bind(self, foo)` | ✅
|
116
|
-
| `T.cast(foo, bar)` | ✅
|
117
|
-
| `T.let(foo, bar)` | ✅
|
118
|
-
| `T.must(foo)` | ✅
|
119
|
-
| `T.reveal_type(foo)` | ✅
|
120
|
-
| `T.type_alias { foo }` | ✅
|
121
|
-
| `T.unsafe(foo)` | ✅
|
98
|
+
| Construct | Status | Replacement |
|
99
|
+
| --------------------------------------------------- | ------ | ----------- |
|
100
|
+
| `# typed: foo` | ✅ | `# ` |
|
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` |
|
122
122
|
|
123
123
|
In the above table, for `Status`:
|
124
124
|
|
@@ -2,21 +2,22 @@
|
|
2
2
|
|
3
3
|
require "sorbet/eraser"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
module Sorbet::Eraser::Patch
|
12
|
-
def input_to_storage(contents, filepath)
|
13
|
-
erased = Sorbet::Eraser.erase(contents)
|
14
|
-
RubyVM::InstructionSequence.compile(erased, filepath, filepath).to_binary
|
15
|
-
rescue SyntaxError
|
16
|
-
raise ::Bootsnap::CompileCache::Uncompilable, "syntax error"
|
17
|
-
end
|
5
|
+
if RubyVM::InstructionSequence.method_defined?(:load_iseq) &&
|
6
|
+
RubyVM::InstructionSequence.method(:load_iseq).source_location[0].include?("/bootsnap/")
|
7
|
+
# If the load_iseq method is defined by bootsnap, then we need to override it.
|
8
|
+
module Sorbet::Eraser::Patch
|
9
|
+
def input_to_storage(contents, filepath)
|
10
|
+
super(Sorbet::Eraser.erase(contents), filepath)
|
18
11
|
end
|
12
|
+
end
|
19
13
|
|
20
|
-
|
14
|
+
Bootsnap::CompileCache::ISeq.singleton_class.prepend(Sorbet::Eraser::Patch)
|
15
|
+
else
|
16
|
+
# Otherwise if the method isn't defined by bootsnap, then we'll define it
|
17
|
+
# ourselves.
|
18
|
+
def (RubyVM::InstructionSequence).load_iseq(filepath)
|
19
|
+
contents = File.read(filepath)
|
20
|
+
erased = Sorbet::Eraser.erase(contents)
|
21
|
+
RubyVM::InstructionSequence.compile(erased, filepath, filepath)
|
21
22
|
end
|
22
23
|
end
|
data/lib/sorbet/eraser/cli.rb
CHANGED
@@ -7,9 +7,10 @@ module Sorbet
|
|
7
7
|
class CLI
|
8
8
|
POOL_SIZE = 4
|
9
9
|
|
10
|
-
attr_reader :filepaths
|
10
|
+
attr_reader :verify, :filepaths
|
11
11
|
|
12
|
-
def initialize(filepaths)
|
12
|
+
def initialize(verify, filepaths)
|
13
|
+
@verify = verify
|
13
14
|
@filepaths = filepaths
|
14
15
|
end
|
15
16
|
|
@@ -28,20 +29,36 @@ module Sorbet
|
|
28
29
|
break if filepath == :eoq
|
29
30
|
process(filepath)
|
30
31
|
end
|
31
|
-
end
|
32
|
+
end.tap { |thread| thread.abort_on_exception = true }
|
32
33
|
end
|
33
34
|
|
34
35
|
workers.each(&:join)
|
35
36
|
end
|
36
37
|
|
37
38
|
def self.start(argv)
|
38
|
-
|
39
|
+
verify = false
|
40
|
+
|
41
|
+
if argv.first == "--verify"
|
42
|
+
verify = true
|
43
|
+
argv.shift
|
44
|
+
end
|
45
|
+
|
46
|
+
filepaths = []
|
47
|
+
argv.each { |pattern| filepaths.concat(Dir.glob(pattern)) }
|
48
|
+
|
49
|
+
new(verify, filepaths).start
|
39
50
|
end
|
40
51
|
|
41
52
|
private
|
42
53
|
|
43
54
|
def process(filepath)
|
44
|
-
|
55
|
+
contents = Eraser.erase(File.read(filepath))
|
56
|
+
|
57
|
+
if verify && Ripper.sexp_raw(contents).nil?
|
58
|
+
warn("Internal error while parsing #{filepath}")
|
59
|
+
else
|
60
|
+
File.write(filepath, contents)
|
61
|
+
end
|
45
62
|
rescue Parser::ParsingError => error
|
46
63
|
warn("Could not parse #{filepath}: #{error}")
|
47
64
|
rescue => error
|
data/lib/sorbet/eraser/parser.rb
CHANGED
@@ -136,18 +136,18 @@ module Sorbet
|
|
136
136
|
|
137
137
|
# Better location information for aref.
|
138
138
|
def on_aref(recv, arg)
|
139
|
-
rend = arg.range.end + source
|
139
|
+
rend = arg.range.end + source.byteslice(arg.range.end..).index("]") + 1
|
140
140
|
Node.new(:aref, [recv, arg], recv.range.begin...rend)
|
141
141
|
end
|
142
142
|
|
143
143
|
# Better location information for arg_paren.
|
144
144
|
def on_arg_paren(arg)
|
145
145
|
if arg
|
146
|
-
rbegin = source
|
147
|
-
rend = arg.range.end + source
|
146
|
+
rbegin = source.byteslice(..arg.range.begin).rindex("(")
|
147
|
+
rend = arg.range.end + source.byteslice(arg.range.end..).index(")") + 1
|
148
148
|
Node.new(:arg_paren, [arg], rbegin...rend)
|
149
149
|
else
|
150
|
-
segment = source
|
150
|
+
segment = source.byteslice(..loc)
|
151
151
|
Node.new(:arg_paren, [arg], segment.rindex("(")...(segment.rindex(")") + 1))
|
152
152
|
end
|
153
153
|
end
|
@@ -159,11 +159,11 @@ module Sorbet
|
|
159
159
|
def on_array(arg)
|
160
160
|
case arg&.event
|
161
161
|
when nil
|
162
|
-
segment = source
|
162
|
+
segment = source.byteslice(..loc)
|
163
163
|
Node.new(:array, [arg], segment.rindex("[")...(segment.rindex("]") + 1))
|
164
164
|
when :qsymbols, :qwords, :symbols, :words
|
165
|
-
rbegin = source
|
166
|
-
rend = source
|
165
|
+
rbegin = source.byteslice(...arg.range.begin).rindex(LISTS.fetch(arg.event))
|
166
|
+
rend = source.byteslice(arg.range.end..).index(TERMINATORS.fetch(source.byteslice(rbegin + 2)) { source.byteslice(rbegin + 2) }) + arg.range.end + 1
|
167
167
|
Node.new(:array, [arg], rbegin...rend)
|
168
168
|
else
|
169
169
|
Node.new(:array, [arg], arg.range)
|
@@ -173,14 +173,14 @@ module Sorbet
|
|
173
173
|
# Better location information for brace_block.
|
174
174
|
def on_brace_block(params, body)
|
175
175
|
if params || body.range
|
176
|
-
rbegin = source
|
176
|
+
rbegin = source.byteslice(...(params || body).range.begin).rindex("{")
|
177
177
|
|
178
178
|
rend = body.range&.end || params.range.end
|
179
|
-
rend = rend + source
|
179
|
+
rend = rend + source.byteslice(rend..).index("}") + 1
|
180
180
|
|
181
181
|
Node.new(:brace_block, [params, body], rbegin...rend)
|
182
182
|
else
|
183
|
-
segment = source
|
183
|
+
segment = source.byteslice(..loc)
|
184
184
|
Node.new(:brace_block, [params, body], segment.rindex("{")...(segment.rindex("}") + 1))
|
185
185
|
end
|
186
186
|
end
|
@@ -188,14 +188,14 @@ module Sorbet
|
|
188
188
|
# Better location information for do_block.
|
189
189
|
def on_do_block(params, body)
|
190
190
|
if params || body.range
|
191
|
-
rbegin = source
|
191
|
+
rbegin = source.byteslice(...(params || body).range.begin).rindex("do")
|
192
192
|
|
193
193
|
rend = body.range&.end || params.range.end
|
194
|
-
rend = rend + source
|
194
|
+
rend = rend + source.byteslice(rend..).index("end") + 3
|
195
195
|
|
196
196
|
Node.new(:do_block, [params, body], rbegin...rend)
|
197
197
|
else
|
198
|
-
segment = source
|
198
|
+
segment = source.byteslice(..loc)
|
199
199
|
Node.new(:do_block, [params, body], segment.rindex("do")...(segment.rindex("end") + 3))
|
200
200
|
end
|
201
201
|
end
|
@@ -205,7 +205,7 @@ module Sorbet
|
|
205
205
|
if arg
|
206
206
|
Node.new(:hash, [arg], arg.range)
|
207
207
|
else
|
208
|
-
segment = source
|
208
|
+
segment = source.byteslice(..loc)
|
209
209
|
Node.new(:hash, [arg], segment.rindex("{")...(segment.rindex("}") + 1))
|
210
210
|
end
|
211
211
|
end
|
@@ -239,7 +239,7 @@ module Sorbet
|
|
239
239
|
if heredoc = heredocs.find { |(_, _, end_arg)| end_arg }
|
240
240
|
Node.new(:string_literal, [arg], heredocs.delete(heredoc)[0])
|
241
241
|
else
|
242
|
-
Node.new(:string_literal, [arg], arg.range)
|
242
|
+
Node.new(:string_literal, [arg], (arg.range.begin - 1)...(arg.range.end + 1))
|
243
243
|
end
|
244
244
|
end
|
245
245
|
|
@@ -13,14 +13,11 @@ module Sorbet
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def erase(source)
|
16
|
-
|
17
|
-
|
16
|
+
encoding = source.encoding
|
17
|
+
source.force_encoding(Encoding::ASCII_8BIT)
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
source[range] = replaced
|
23
|
-
source
|
19
|
+
source[range] = replace(source[range])
|
20
|
+
source.force_encoding(encoding)
|
24
21
|
end
|
25
22
|
|
26
23
|
def blank(segment)
|
@@ -57,11 +54,11 @@ module Sorbet
|
|
57
54
|
# We can't really rely on regex here because commas have semantic
|
58
55
|
# meaning and you might have some in the value of the first argument.
|
59
56
|
comma = metadata.fetch(:comma)
|
60
|
-
pre, post = 0
|
57
|
+
pre, post = 0...comma, comma..-1
|
61
58
|
|
62
59
|
replacement[pre] =
|
63
|
-
replacement[pre].gsub(/(T\s*\.(?:assert_type!|bind|cast|let)\(\s*)(.+)
|
64
|
-
"#{blank($1)}#{$2}
|
60
|
+
replacement[pre].gsub(/(T\s*\.(?:assert_type!|bind|cast|let)\(\s*)(.+)/m) do
|
61
|
+
"#{blank($1)}#{$2}"
|
65
62
|
end
|
66
63
|
|
67
64
|
replacement[post] = blank(replacement[post])
|
@@ -100,24 +97,26 @@ module Sorbet
|
|
100
97
|
end
|
101
98
|
end
|
102
99
|
|
103
|
-
# typed: ignore
|
104
|
-
# typed: false
|
105
|
-
# typed: true
|
106
|
-
# typed:
|
100
|
+
# typed: ignore => #
|
101
|
+
# typed: false => #
|
102
|
+
# typed: true => #
|
103
|
+
# typed: strict => #
|
104
|
+
# typed: strong => #
|
107
105
|
class TypedCommentPattern < Pattern
|
108
106
|
def replace(segment)
|
109
|
-
segment.gsub(
|
110
|
-
"
|
107
|
+
segment.gsub(/\A#(\s*typed:\s*(?:ignore|false|true|strict|strong)(\s*))\z/) do
|
108
|
+
"##{blank($1)}"
|
111
109
|
end
|
112
110
|
end
|
113
111
|
end
|
114
112
|
|
115
113
|
def on_comment(comment)
|
116
114
|
super.tap do |node|
|
117
|
-
if lineno == 1 && comment.match?(/\A#\s*typed:\s*(?:ignore|false|true|strong)\s*\z/)
|
115
|
+
if lineno == 1 && comment.match?(/\A#\s*typed:\s*(?:ignore|false|true|strict|strong)\s*\z/)
|
118
116
|
# typed: ignore
|
119
117
|
# typed: false
|
120
118
|
# typed: true
|
119
|
+
# typed: strict
|
121
120
|
# typed: strong
|
122
121
|
patterns << TypedCommentPattern.new(node.range)
|
123
122
|
end
|
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.5.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-07-
|
11
|
+
date: 2023-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -89,7 +89,7 @@ licenses:
|
|
89
89
|
- MIT
|
90
90
|
metadata:
|
91
91
|
bug_tracker_uri: https://github.com/kddnewton/sorbet-eraser/issues
|
92
|
-
changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.
|
92
|
+
changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.5.0/CHANGELOG.md
|
93
93
|
source_code_uri: https://github.com/kddnewton/sorbet-eraser
|
94
94
|
rubygems_mfa_required: 'true'
|
95
95
|
post_install_message:
|