sorbet-eraser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +22 -0
- data/LICENSE +21 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/bin/console +7 -0
- data/bin/setup +6 -0
- data/lib/sorbet/eraser.rb +30 -0
- data/lib/sorbet/eraser/autoload.rb +22 -0
- data/lib/sorbet/eraser/parser.rb +147 -0
- data/lib/sorbet/eraser/patterns.rb +173 -0
- data/lib/sorbet/eraser/version.rb +7 -0
- data/sorbet-eraser.gemspec +27 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 839924a4d5d33c9f00287b6be5151b263ef6b4446b524568ee0bbb212f2a4584
|
4
|
+
data.tar.gz: 5658154f4303ffa3f547992d679f62efa5f613eb0cf7cb871e51cecb21d7c329
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae54ad36caaee0aaf2f70eba22fa6e1e9fe3b00b682d00a2007b50076e211d5e4922474a59820193a673e26f76f3b0f63bc2662021577e07cccc5f9d5a8ec55f
|
7
|
+
data.tar.gz: 257cb54108d56dbe36bf10316a2191bc594c59bfa4c4eff358b005703b2a01767ca432c558cc6785f5ad0ac4a918eddef3fca3c15b1c3b17941ca085c8f784fd
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sorbet-eraser (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.14.4)
|
10
|
+
rake (13.0.6)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
x86_64-darwin-19
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
bundler
|
17
|
+
minitest
|
18
|
+
rake
|
19
|
+
sorbet-eraser!
|
20
|
+
|
21
|
+
BUNDLED WITH
|
22
|
+
2.2.24
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021-present Kevin Newton
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Sorbet::Eraser
|
2
|
+
|
3
|
+
Erase all traces of `sorbet-runtime` code.
|
4
|
+
|
5
|
+
`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.
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'sorbet-eraser'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install sorbet-eraser
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
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.
|
28
|
+
|
29
|
+
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.
|
30
|
+
|
31
|
+
### Status
|
32
|
+
|
33
|
+
Below is a table of the status of each `sorbet-runtime` construct and its current support status.
|
34
|
+
|
35
|
+
| Construct | Status | Replacement |
|
36
|
+
| --------- | ------ | ----------- |
|
37
|
+
| `include T::Generic` | 🛠| |
|
38
|
+
| `include T::Helpers` | 🛠| |
|
39
|
+
| `extend T::Sig` | ✅ | |
|
40
|
+
| `class Foo < T::Enum` | 🛠| `class Foo < ::Sorbet::Eraser::Enum` |
|
41
|
+
| `class Foo < T::Struct` | 🛠| `class Foo < ::Sorbet::Eraser::Struct` |
|
42
|
+
| `abstract!` | 🛠| |
|
43
|
+
| `final!` | 🛠| |
|
44
|
+
| `interface!` | 🛠| |
|
45
|
+
| `mixes_in_class_methods(foo)` | 🛠| `foo` |
|
46
|
+
| `sig` | ✅ | |
|
47
|
+
| `T.absurd(foo)` | ✅ | `raise ::Sorbet::Eraser::AbsurdError` |
|
48
|
+
| `T.assert_type!(foo)` | ✅ | `foo` |
|
49
|
+
| `T.bind(self, foo)` | ✅ | `self` |
|
50
|
+
| `T.cast(foo, bar)` | ✅ | `foo` |
|
51
|
+
| `T.let(foo, bar)` | ✅ | `foo` |
|
52
|
+
| `T.must(foo)` | ✅ | `foo` |
|
53
|
+
| `T.must foo` | ✅ | `foo` |
|
54
|
+
| `T.reveal_type(foo)` | ✅ | `foo` |
|
55
|
+
| `T.type_alias { foo }` | ✅ | `::Sorbet::Eraser::TypeAlias` |
|
56
|
+
| `T.unsafe(foo)` | 🛠| `foo` |
|
57
|
+
|
58
|
+
## Development
|
59
|
+
|
60
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
61
|
+
|
62
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kddnewton/sorbet-eraser.
|
67
|
+
|
68
|
+
## License
|
69
|
+
|
70
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ripper"
|
4
|
+
|
5
|
+
require "sorbet/eraser/parser"
|
6
|
+
require "sorbet/eraser/patterns"
|
7
|
+
require "sorbet/eraser/version"
|
8
|
+
|
9
|
+
module Sorbet
|
10
|
+
module Eraser
|
11
|
+
# This error gets raise in place of any T.absurd calls.
|
12
|
+
class AbsurdError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
# This class gets put in place of any existing T.type_alias calls.
|
16
|
+
class TypeAlias
|
17
|
+
end
|
18
|
+
|
19
|
+
# Hook the patterns into the parser so that the correct methods get
|
20
|
+
# overridden and will trigger replacements.
|
21
|
+
Parser.prepend(Patterns)
|
22
|
+
|
23
|
+
# The entrypoint method to this overall module. This should be called with a
|
24
|
+
# string that represents Ruby source, and it will return the modified Ruby
|
25
|
+
# source.
|
26
|
+
def self.erase(source)
|
27
|
+
Parser.erase(source)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sorbet/eraser"
|
4
|
+
|
5
|
+
# Hook into bootsnap so that before the source is compiled through
|
6
|
+
# RubyVM::InstructionSequence it gets erased through the eraser.
|
7
|
+
if RubyVM::InstructionSequence.method_defined?(:load_iseq)
|
8
|
+
load_iseq, = RubyVM::InstructionSequence.method(:load_iseq).source_location
|
9
|
+
|
10
|
+
if load_iseq.include?("/bootsnap/")
|
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
|
18
|
+
end
|
19
|
+
|
20
|
+
Bootsnap::CompileCache::ISeq.singleton_class.prepend(Sorbet::Eraser::Patch)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sorbet
|
4
|
+
module Eraser
|
5
|
+
# A Ripper parser that will replace usage of Sorbet patterns with whitespace
|
6
|
+
# so that location information is maintained but Sorbet methods aren't
|
7
|
+
# called.
|
8
|
+
class Parser < Ripper
|
9
|
+
# Represents a line in the source. If this class is being used, it means
|
10
|
+
# that every character in the string is 1 byte in length, so we can just
|
11
|
+
# return the start of the line + the index.
|
12
|
+
class SingleByteString
|
13
|
+
def initialize(start)
|
14
|
+
@start = start
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](byteindex)
|
18
|
+
@start + byteindex
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Represents a line in the source. If this class is being used, it means
|
23
|
+
# that there are characters in the string that are multi-byte, so we will
|
24
|
+
# build up an array of indices, such that array[byteindex] will be equal
|
25
|
+
# to the index of the character within the string.
|
26
|
+
class MultiByteString
|
27
|
+
def initialize(start, line)
|
28
|
+
@indices = []
|
29
|
+
|
30
|
+
line
|
31
|
+
.each_char
|
32
|
+
.with_index(start) do |char, index|
|
33
|
+
char.bytesize.times { @indices << index }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](byteindex)
|
38
|
+
@indices[byteindex]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Represents a node in the AST. Keeps track of the event that generated
|
43
|
+
# it, any child nodes that descend from it, and the location in the
|
44
|
+
# source.
|
45
|
+
class Node
|
46
|
+
attr_reader :event, :body, :range
|
47
|
+
|
48
|
+
def initialize(event, body, range)
|
49
|
+
@event = event
|
50
|
+
@body = body
|
51
|
+
@range = range
|
52
|
+
end
|
53
|
+
|
54
|
+
def match?(pattern)
|
55
|
+
to_s.match?(pattern)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
@to_s ||= "<#{event} #{body.map(&:to_s).join(" ")}>"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :line_counts, :patterns
|
64
|
+
|
65
|
+
def initialize(source)
|
66
|
+
super(source)
|
67
|
+
|
68
|
+
@line_counts = []
|
69
|
+
last_index = 0
|
70
|
+
|
71
|
+
source.lines.each do |line|
|
72
|
+
if line.size == line.bytesize
|
73
|
+
@line_counts << SingleByteString.new(last_index)
|
74
|
+
else
|
75
|
+
@line_counts << MultiByteString.new(last_index, line)
|
76
|
+
end
|
77
|
+
|
78
|
+
last_index += line.size
|
79
|
+
end
|
80
|
+
|
81
|
+
@patterns = []
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.erase(source)
|
85
|
+
parser = new(source)
|
86
|
+
|
87
|
+
if parser.parse.nil? || parser.error?
|
88
|
+
raise "Invalid source"
|
89
|
+
else
|
90
|
+
parser.patterns.inject(source) do |current, pattern|
|
91
|
+
pattern.erase(current)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def loc
|
99
|
+
line_counts[lineno - 1][column]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Loop through all of the scanner events and define a basic method that
|
103
|
+
# wraps everything into a node class.
|
104
|
+
SCANNER_EVENTS.each do |event|
|
105
|
+
define_method(:"on_#{event}") do |value|
|
106
|
+
range = loc.then { |start| start..(start + (value&.size || 0)) }
|
107
|
+
Node.new(:"@#{event}", [value], range)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Loop through the parser events and generate a method for each event. If
|
112
|
+
# it's one of the _new methods, then use arrays like SexpBuilderPP. If
|
113
|
+
# it's an _add method then just append to the array. If it's a normal
|
114
|
+
# method, then create a new node and determine its bounds.
|
115
|
+
PARSER_EVENT_TABLE.each do |event, arity|
|
116
|
+
case event
|
117
|
+
when /\A(.+)_new\z/
|
118
|
+
prefix = $1.to_sym
|
119
|
+
|
120
|
+
define_method(:"on_#{event}") do
|
121
|
+
Node.new(prefix, [], loc.then { |start| start..start })
|
122
|
+
end
|
123
|
+
when /_add\z/
|
124
|
+
define_method(:"on_#{event}") do |node, value|
|
125
|
+
range =
|
126
|
+
if node.body.empty?
|
127
|
+
value.range
|
128
|
+
else
|
129
|
+
(node.range.begin..value.range.end)
|
130
|
+
end
|
131
|
+
|
132
|
+
node.class.new(node.event, node.body + [value], range)
|
133
|
+
end
|
134
|
+
else
|
135
|
+
define_method(:"on_#{event}") do |*args|
|
136
|
+
first, *, last = args.grep(Node).map(&:range)
|
137
|
+
|
138
|
+
first ||= loc.then { |start| start..start }
|
139
|
+
last ||= first
|
140
|
+
|
141
|
+
Node.new(event, args, first.begin..[last.end, loc].max)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sorbet
|
4
|
+
module Eraser
|
5
|
+
module Patterns
|
6
|
+
# A pattern in code that represents a call to a special Sorbet method.
|
7
|
+
class Pattern
|
8
|
+
attr_reader :range
|
9
|
+
|
10
|
+
def initialize(range)
|
11
|
+
@range = range
|
12
|
+
end
|
13
|
+
|
14
|
+
def erase(source)
|
15
|
+
original = source[range]
|
16
|
+
replaced = replace(original)
|
17
|
+
|
18
|
+
# puts "Replacing #{original} (len=#{original.length}) " \
|
19
|
+
# "with #{replaced} (len=#{replaced.length})"
|
20
|
+
|
21
|
+
source[range] = replaced
|
22
|
+
source
|
23
|
+
end
|
24
|
+
|
25
|
+
def replace(segment)
|
26
|
+
segment
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# T.absurd(foo) => raise ::Sorbet::Eraser::AbsurdError
|
31
|
+
class TAbsurdParensPattern < Pattern
|
32
|
+
def replace(segment)
|
33
|
+
segment.gsub(/(T\s*\.absurd\(\s*.+\s*\))(.*)/) do
|
34
|
+
replacement = "raise ::Sorbet::Eraser::AbsurdError"
|
35
|
+
"#{replacement}#{" " * [$1.length - replacement.length, 0].max}#{$2}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# T.must(foo) => foo
|
41
|
+
# T.reveal_type(foo) => foo
|
42
|
+
class TOneArgMethodCallParensPattern < Pattern
|
43
|
+
def replace(segment)
|
44
|
+
segment.gsub(/(T\s*\.(?:must|reveal_type)\(\s*)(.+)(\s*\))(.*)/) do
|
45
|
+
"#{" " * $1.length}#{$2}#{" " * $3.length}#{$4}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# T.assert_type!(foo, bar) => foo
|
51
|
+
# T.bind(self, foo) => self
|
52
|
+
# T.cast(foo, bar) => foo
|
53
|
+
# T.let(foo, bar) => let
|
54
|
+
class TTwoArgMethodCallParensPattern < Pattern
|
55
|
+
def replace(segment)
|
56
|
+
segment.gsub(/(T\s*\.(?:assert_type!|bind|cast|let)\(\s*)(.+)(\s*,.+\))(.*)/) do
|
57
|
+
"#{" " * $1.length}#{$2}#{" " * $3.length}#{$4}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_method_add_arg(call, arg_paren)
|
63
|
+
# T.absurd(foo)
|
64
|
+
if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident absurd>>/) &&
|
65
|
+
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
66
|
+
patterns << TAbsurdParensPattern.new(call.range.begin..arg_paren.range.end)
|
67
|
+
end
|
68
|
+
|
69
|
+
# T.must(foo)
|
70
|
+
# T.reveal_type(foo)
|
71
|
+
if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident (?:must|reveal_type)>>/) &&
|
72
|
+
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
73
|
+
patterns << TOneArgMethodCallParensPattern.new(call.range.begin..arg_paren.range.end)
|
74
|
+
end
|
75
|
+
|
76
|
+
# T.assert_type!(foo, bar)
|
77
|
+
# T.cast(foo, bar)
|
78
|
+
# T.let(foo, bar)
|
79
|
+
if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>/) &&
|
80
|
+
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
81
|
+
patterns << TTwoArgMethodCallParensPattern.new(call.range.begin..arg_paren.range.end)
|
82
|
+
end
|
83
|
+
|
84
|
+
# T.bind(self, foo)
|
85
|
+
if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident bind>>/) &&
|
86
|
+
arg_paren.match?(/<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>/)
|
87
|
+
patterns << TTwoArgMethodCallParensPattern.new(call.range.begin..arg_paren.range.end)
|
88
|
+
end
|
89
|
+
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
93
|
+
# T.type_alias { foo } => ::Sorbet::Eraser::TypeAlias
|
94
|
+
class TTypeAliasBraceBlockPattern < Pattern
|
95
|
+
def replace(segment)
|
96
|
+
segment.gsub(/(T\s*\.type_alias\s*\{.*\})(.*)/) do
|
97
|
+
replacement = "::Sorbet::Eraser::TypeAlias"
|
98
|
+
"#{replacement}#{" " * [$1.length - replacement.length, 0].max}#{$2}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def on_method_add_block(method_add_arg, block)
|
104
|
+
# T.type_alias { foo }
|
105
|
+
if method_add_arg.match?("<call <var_ref <@const T>> <@period .> <@ident type_alias>>") &&
|
106
|
+
block.match?(/<brace_block <stmts .+>>/)
|
107
|
+
patterns << TTypeAliasBraceBlockPattern.new(method_add_arg.range.begin..block.range.end)
|
108
|
+
end
|
109
|
+
|
110
|
+
super
|
111
|
+
end
|
112
|
+
|
113
|
+
# extend T::Sig =>
|
114
|
+
class ExtendTSigPattern < Pattern
|
115
|
+
def replace(segment)
|
116
|
+
segment.gsub(/(extend\s+T::Sig)(.*)/) do
|
117
|
+
"#{" " * $1.length}#{$2}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def on_command(ident, args_add_block)
|
123
|
+
# extend T::Sig
|
124
|
+
if ident.match?("<@ident extend>") &&
|
125
|
+
args_add_block.match?("<args_add_block <args <const_path_ref <var_ref <@const T>> <@const Sig>>> false>")
|
126
|
+
patterns << ExtendTSigPattern.new(ident.range.begin..args_add_block.range.end)
|
127
|
+
end
|
128
|
+
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
# T.must foo => foo
|
133
|
+
class TMustNoParensPattern < Pattern
|
134
|
+
def replace(segment)
|
135
|
+
segment.gsub(/(T\s*\.must\s*)(.+)/) do
|
136
|
+
"#{" " * $1.length}#{$2}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def on_command_call(var_ref, period, ident, args_add_block)
|
142
|
+
if var_ref.match?("<var_ref <@const T>>") && period.match?("<@period .>")
|
143
|
+
# T.must foo
|
144
|
+
if ident.match?("<@ident must>") &&
|
145
|
+
args_add_block.match?(/<args_add_block <args <.+>> false>/) &&
|
146
|
+
args_add_block.body[0].body.length == 1
|
147
|
+
patterns << TMustNoParensPattern.new(var_ref.range.begin..args_add_block.range.end)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
super
|
152
|
+
end
|
153
|
+
|
154
|
+
# sig { foo } =>
|
155
|
+
class SigBracesPattern < Pattern
|
156
|
+
def replace(segment)
|
157
|
+
segment.gsub(/(sig\s*\{.+\})(.*)/) do
|
158
|
+
"#{" " * $1.length}#{$2}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def on_stmts_add(node, value)
|
164
|
+
# sig { foo }
|
165
|
+
if value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>/)
|
166
|
+
patterns << SigBracesPattern.new(value.range)
|
167
|
+
end
|
168
|
+
|
169
|
+
super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "sorbet/eraser/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "sorbet-eraser"
|
9
|
+
spec.version = Sorbet::Eraser::VERSION
|
10
|
+
spec.authors = ["Kevin Newton"]
|
11
|
+
spec.email = ["kddnewton@gmail.com"]
|
12
|
+
|
13
|
+
spec.summary = "Erase all traces of sorbet-runtime code."
|
14
|
+
spec.homepage = "https://github.com/kddnewton/sorbet-eraser"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "minitest"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sorbet-eraser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Newton
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- kddnewton@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- Gemfile.lock
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/setup
|
70
|
+
- lib/sorbet/eraser.rb
|
71
|
+
- lib/sorbet/eraser/autoload.rb
|
72
|
+
- lib/sorbet/eraser/parser.rb
|
73
|
+
- lib/sorbet/eraser/patterns.rb
|
74
|
+
- lib/sorbet/eraser/version.rb
|
75
|
+
- sorbet-eraser.gemspec
|
76
|
+
homepage: https://github.com/kddnewton/sorbet-eraser
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.2.3
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Erase all traces of sorbet-runtime code.
|
99
|
+
test_files: []
|