sorbet-eraser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sorbet/eraser"
5
+
6
+ require "irb"
7
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sorbet
4
+ module Eraser
5
+ VERSION = "0.1.0"
6
+ end
7
+ 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: []