sorbet-eraser 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/main.yml +32 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +6 -3
- data/README.md +48 -25
- data/exe/sorbet-eraser +7 -0
- data/lib/sorbet/eraser/cli.rb +52 -0
- data/lib/sorbet/eraser/parser.rb +21 -6
- data/lib/sorbet/eraser/patterns.rb +96 -62
- data/lib/sorbet/eraser/version.rb +1 -1
- data/lib/sorbet/eraser.rb +1 -8
- data/lib/t/enum.rb +252 -0
- data/lib/t/props.rb +59 -0
- data/lib/t/struct.rb +52 -0
- data/lib/t.rb +47 -0
- data/sorbet-eraser.gemspec +15 -6
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efd5822039bf577e0c4a37171d7e91abd9c7bed250c9d09c779f52faa0d84cf6
|
4
|
+
data.tar.gz: 1a88eab81b0932ab400f5aeaf08e43cb5107fc08c135e232cc444205f76fdcf3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a64889bcbdf6affe397a394ed3a0841a166ff376e806e050967d4676910670a5f75d774e25c2490fae410c2b5e9263f07c07597ae6c0a0c08ae226343032ed0f
|
7
|
+
data.tar.gz: 50b223fde3f5901535888d6e06f685ba128c6bda051d516fe37f8b825ce9c328bfddaff54bb19a52aa1694309be11a74732ceb0b582e00179cf939d8eba760ba
|
@@ -0,0 +1,32 @@
|
|
1
|
+
name: Main
|
2
|
+
on:
|
3
|
+
- push
|
4
|
+
- pull_request_target
|
5
|
+
jobs:
|
6
|
+
ci:
|
7
|
+
name: CI
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
env:
|
10
|
+
CI: true
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@master
|
13
|
+
- uses: ruby/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: '3.1'
|
16
|
+
bundler-cache: true
|
17
|
+
- name: Test
|
18
|
+
run: bundle exec rake test
|
19
|
+
automerge:
|
20
|
+
name: AutoMerge
|
21
|
+
needs: ci
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
if: github.event_name == 'pull_request_target' && (github.actor == github.repository_owner || github.actor == 'dependabot[bot]')
|
24
|
+
steps:
|
25
|
+
- uses: actions/github-script@v3
|
26
|
+
with:
|
27
|
+
script: |
|
28
|
+
github.pulls.merge({
|
29
|
+
owner: context.payload.repository.owner.login,
|
30
|
+
repo: context.payload.repository.name,
|
31
|
+
pull_number: context.payload.pull_request.number
|
32
|
+
})
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
|
9
|
+
## [0.1.1] - 2021-11-17
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
|
13
|
+
- Require MFA for releasing.
|
14
|
+
|
15
|
+
[unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.1.1...HEAD
|
16
|
+
[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.2.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
|
@@ -19,4 +22,4 @@ DEPENDENCIES
|
|
19
22
|
sorbet-eraser!
|
20
23
|
|
21
24
|
BUNDLED WITH
|
22
|
-
2.2.
|
25
|
+
2.2.15
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Sorbet::Eraser
|
2
2
|
|
3
|
+
[![Build Status](https://github.com/kddnewton/sorbet-eraser/workflows/Main/badge.svg)](https://github.com/kddnewton/sorbet-eraser/actions)
|
4
|
+
[![Gem](https://img.shields.io/gem/v/sorbet-eraser.svg)](https://rubygems.org/gems/sorbet-eraser)
|
5
|
+
|
3
6
|
Erase all traces of `sorbet-runtime` code.
|
4
7
|
|
5
8
|
`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.
|
@@ -11,7 +14,7 @@ This gem takes a different approach, but entirely eliminating the `sig` method c
|
|
11
14
|
Add this line to your application's Gemfile:
|
12
15
|
|
13
16
|
```ruby
|
14
|
-
gem
|
17
|
+
gem "sorbet-eraser"
|
15
18
|
```
|
16
19
|
|
17
20
|
And then execute:
|
@@ -24,36 +27,56 @@ Or install it yourself as:
|
|
24
27
|
|
25
28
|
## Usage
|
26
29
|
|
27
|
-
|
30
|
+
There are two ways to use this gem, depending on your needs.
|
31
|
+
|
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
|
+
|
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
|
+
|
36
|
+
```bash
|
37
|
+
bundle exec sorbet-eraser '**/*.rb'
|
38
|
+
```
|
28
39
|
|
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.
|
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.
|
30
41
|
|
31
42
|
### Status
|
32
43
|
|
33
44
|
Below is a table of the status of each `sorbet-runtime` construct and its current support status.
|
34
45
|
|
35
|
-
| Construct
|
36
|
-
|
|
37
|
-
| `
|
38
|
-
| `
|
39
|
-
| `
|
40
|
-
| `
|
41
|
-
| `class Foo < T::
|
42
|
-
| `
|
43
|
-
| `
|
44
|
-
| `
|
45
|
-
| `
|
46
|
-
| `
|
47
|
-
| `T
|
48
|
-
| `
|
49
|
-
| `T.
|
50
|
-
| `T.
|
51
|
-
| `T.
|
52
|
-
| `T.
|
53
|
-
| `T.
|
54
|
-
| `T.
|
55
|
-
| `T.
|
56
|
-
| `T.
|
46
|
+
| Construct | Status | Replacement |
|
47
|
+
| --------------------------------------------------- | ------ | ----------- |
|
48
|
+
| `extend T::*` | ✅ | Shimmed |
|
49
|
+
| `abstract!`, `final!`, `interface!`, `sealed!` | ✅ | Shimmed |
|
50
|
+
| `mixes_in_class_methods(*)`, `requires_ancestor(*)` | ✅ | Shimmed |
|
51
|
+
| `type_member(*)`, `type_template(*)` | ✅ | Shimmed |
|
52
|
+
| `class Foo < T::Enum` | ✅ | Shimmed |
|
53
|
+
| `class Foo < T::InexactStruct` | 🛠 | Shimmed |
|
54
|
+
| `class Foo < T::Struct` | 🛠 | Shimmed |
|
55
|
+
| `class Foo < T::ImmutableStruct` | 🛠 | Shimmed |
|
56
|
+
| `include T::Props` | 🛠 | Shimmed |
|
57
|
+
| `include T::Props::Serializable` | 🛠 | Shimmed |
|
58
|
+
| `include T::Props::Constructor` | 🛠 | Shimmed |
|
59
|
+
| `sig` | ✅ | Removed |
|
60
|
+
| `T.absurd(foo)` | ✅ | Shimmed |
|
61
|
+
| `T.assert_type!(foo, bar)` | ✅ | `foo` |
|
62
|
+
| `T.bind(self, foo)` | ✅ | `self` |
|
63
|
+
| `T.cast(foo, bar)` | ✅ | `foo` |
|
64
|
+
| `T.let(foo, bar)` | ✅ | `foo` |
|
65
|
+
| `T.must(foo)` | ✅ | `foo` |
|
66
|
+
| `T.reveal_type(foo)` | ✅ | `foo` |
|
67
|
+
| `T.type_alias { foo }` | ✅ | Shimmed |
|
68
|
+
| `T.unsafe(foo)` | ✅ | `foo` |
|
69
|
+
|
70
|
+
In the above table, for `Status`:
|
71
|
+
|
72
|
+
* ✅ means that we are confident this is replaced 1:1.
|
73
|
+
* 🛠 means there may be APIs that are not entirely supported. If you run into something that is missing, please open an issue.
|
74
|
+
|
75
|
+
In the above table, for `Replacement`:
|
76
|
+
|
77
|
+
* `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.
|
78
|
+
* `Removed` means that the construct is removed entirely from the source.
|
79
|
+
* Anything else means that the inputted code is replaced with that output.
|
57
80
|
|
58
81
|
## Development
|
59
82
|
|
data/exe/sorbet-eraser
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sorbet
|
4
|
+
module Eraser
|
5
|
+
# A small CLI that takes filepaths and erases them by writing back to the
|
6
|
+
# original file.
|
7
|
+
class CLI
|
8
|
+
POOL_SIZE = 4
|
9
|
+
|
10
|
+
attr_reader :filepaths
|
11
|
+
|
12
|
+
def initialize(filepaths)
|
13
|
+
@filepaths = filepaths
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
queue = Queue.new
|
18
|
+
filepaths.each { |filepath| queue << filepath }
|
19
|
+
|
20
|
+
workers =
|
21
|
+
POOL_SIZE.times.map do
|
22
|
+
# push a symbol onto the queue for each thread so that it knows when
|
23
|
+
# the end of the queue is and will exit its infinite loop
|
24
|
+
queue << :eoq
|
25
|
+
|
26
|
+
Thread.new do
|
27
|
+
while filepath = queue.shift
|
28
|
+
break if filepath == :eoq
|
29
|
+
process(filepath)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
workers.each(&:join)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.start(argv)
|
38
|
+
new(argv.flat_map { |pattern| Dir.glob(pattern) }).start
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def process(filepath)
|
44
|
+
File.write(filepath, Eraser.erase(File.read(filepath)))
|
45
|
+
rescue Parser::ParsingError => error
|
46
|
+
warn("Could not parse #{filepath}: #{error}")
|
47
|
+
rescue => error
|
48
|
+
warn("Could not parse #{filepath}: #{error}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/sorbet/eraser/parser.rb
CHANGED
@@ -35,7 +35,10 @@ module Sorbet
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def [](byteindex)
|
38
|
-
|
38
|
+
# Why the || byteindex? I'm not sure. For some reason ripper is
|
39
|
+
# returning very odd column values when you have a multibyte line.
|
40
|
+
# This is the only way I could find to make it work.
|
41
|
+
@indices[byteindex] || byteindex
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
@@ -60,11 +63,16 @@ module Sorbet
|
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
|
-
|
66
|
+
# Raised in the case that source can't be parsed.
|
67
|
+
class ParsingError < StandardError
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :source, :line_counts, :errors, :patterns
|
64
71
|
|
65
72
|
def initialize(source)
|
66
73
|
super(source)
|
67
74
|
|
75
|
+
@source = source
|
68
76
|
@line_counts = []
|
69
77
|
last_index = 0
|
70
78
|
|
@@ -78,6 +86,7 @@ module Sorbet
|
|
78
86
|
last_index += line.size
|
79
87
|
end
|
80
88
|
|
89
|
+
@errors = []
|
81
90
|
@patterns = []
|
82
91
|
end
|
83
92
|
|
@@ -85,7 +94,7 @@ module Sorbet
|
|
85
94
|
parser = new(source)
|
86
95
|
|
87
96
|
if parser.parse.nil? || parser.error?
|
88
|
-
raise
|
97
|
+
raise ParsingError, parser.errors.join("\n")
|
89
98
|
else
|
90
99
|
parser.patterns.inject(source) do |current, pattern|
|
91
100
|
pattern.erase(current)
|
@@ -113,14 +122,13 @@ module Sorbet
|
|
113
122
|
# it's an _add method then just append to the array. If it's a normal
|
114
123
|
# method, then create a new node and determine its bounds.
|
115
124
|
PARSER_EVENT_TABLE.each do |event, arity|
|
116
|
-
|
117
|
-
when /\A(.+)_new\z/
|
125
|
+
if event =~ /\A(.+)_new\z/ && event != :assoc_new
|
118
126
|
prefix = $1.to_sym
|
119
127
|
|
120
128
|
define_method(:"on_#{event}") do
|
121
129
|
Node.new(prefix, [], loc.then { |start| start..start })
|
122
130
|
end
|
123
|
-
|
131
|
+
elsif event =~ /_add\z/
|
124
132
|
define_method(:"on_#{event}") do |node, value|
|
125
133
|
range =
|
126
134
|
if node.body.empty?
|
@@ -131,6 +139,8 @@ module Sorbet
|
|
131
139
|
|
132
140
|
node.class.new(node.event, node.body + [value], range)
|
133
141
|
end
|
142
|
+
elsif event == :parse_error
|
143
|
+
# skip this, as we're going to define it below
|
134
144
|
else
|
135
145
|
define_method(:"on_#{event}") do |*args|
|
136
146
|
first, *, last = args.grep(Node).map(&:range)
|
@@ -142,6 +152,11 @@ module Sorbet
|
|
142
152
|
end
|
143
153
|
end
|
144
154
|
end
|
155
|
+
|
156
|
+
# Track the parsing errors for nicer error messages.
|
157
|
+
def on_parse_error(error)
|
158
|
+
errors << "line #{lineno}: #{error}"
|
159
|
+
end
|
145
160
|
end
|
146
161
|
end
|
147
162
|
end
|
@@ -5,10 +5,11 @@ module Sorbet
|
|
5
5
|
module Patterns
|
6
6
|
# A pattern in code that represents a call to a special Sorbet method.
|
7
7
|
class Pattern
|
8
|
-
attr_reader :range
|
8
|
+
attr_reader :range, :metadata
|
9
9
|
|
10
|
-
def initialize(range)
|
10
|
+
def initialize(range, **metadata)
|
11
11
|
@range = range
|
12
|
+
@metadata = metadata
|
12
13
|
end
|
13
14
|
|
14
15
|
def erase(source)
|
@@ -22,27 +23,25 @@ module Sorbet
|
|
22
23
|
source
|
23
24
|
end
|
24
25
|
|
25
|
-
def
|
26
|
-
|
26
|
+
def blank(segment)
|
27
|
+
# This is deceptive in that it hides that it actually replaces
|
28
|
+
# everything with spaces _except_ newline characters, which is keeps
|
29
|
+
# in place.
|
30
|
+
segment.gsub(/./, " ")
|
27
31
|
end
|
28
|
-
end
|
29
32
|
|
30
|
-
# T.absurd(foo) => raise ::Sorbet::Eraser::AbsurdError
|
31
|
-
class TAbsurdParensPattern < Pattern
|
32
33
|
def replace(segment)
|
33
|
-
segment
|
34
|
-
replacement = "raise ::Sorbet::Eraser::AbsurdError"
|
35
|
-
"#{replacement}#{" " * [$1.length - replacement.length, 0].max}#{$2}"
|
36
|
-
end
|
34
|
+
segment
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
40
38
|
# T.must(foo) => foo
|
41
39
|
# T.reveal_type(foo) => foo
|
40
|
+
# T.unsafe(foo) => foo
|
42
41
|
class TOneArgMethodCallParensPattern < Pattern
|
43
42
|
def replace(segment)
|
44
|
-
segment.gsub(/(T\s*\.(?:must|reveal_type)\(\s*)(.+)(\s*\))(.*)/) do
|
45
|
-
"#{
|
43
|
+
segment.gsub(/(T\s*\.(?:must|reveal_type|unsafe)\(\s*)(.+)(\s*\))(.*)/m) do
|
44
|
+
"#{blank($1)}#{$2}#{blank($3)}#{$4}"
|
46
45
|
end
|
47
46
|
end
|
48
47
|
end
|
@@ -53,98 +52,133 @@ module Sorbet
|
|
53
52
|
# T.let(foo, bar) => let
|
54
53
|
class TTwoArgMethodCallParensPattern < Pattern
|
55
54
|
def replace(segment)
|
56
|
-
segment.
|
57
|
-
|
55
|
+
replacement = segment.dup
|
56
|
+
|
57
|
+
# We can't really rely on regex here because commas have semantic
|
58
|
+
# meaning and you might have some in the value of the first argument.
|
59
|
+
comma = metadata.fetch(:comma)
|
60
|
+
pre, post = 0..comma, (comma + 1)..-1
|
61
|
+
|
62
|
+
replacement[pre] =
|
63
|
+
replacement[pre].gsub(/(T\s*\.(?:assert_type!|bind|cast|let)\(\s*)(.+)(\s*,)(.*)/m) do
|
64
|
+
"#{blank($1)}#{$2}#{blank($3)}#{$4}"
|
65
|
+
end
|
66
|
+
|
67
|
+
replacement[post] = blank(replacement[post])
|
68
|
+
replacement
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# abstract! =>
|
73
|
+
# final! =>
|
74
|
+
# interface! =>
|
75
|
+
class DeclarationPattern < Pattern
|
76
|
+
def replace(segment)
|
77
|
+
segment.gsub(/((?:abstract|final|interface)!(?:\(\s*\))?)(.*)/) do
|
78
|
+
"#{blank($1)}#{$2}"
|
58
79
|
end
|
59
80
|
end
|
60
81
|
end
|
61
82
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
83
|
+
# mixes_in_class_methods(foo) => foo
|
84
|
+
class MixesInClassMethodsPattern < Pattern
|
85
|
+
def replace(segment)
|
86
|
+
segment.gsub(/(mixes_in_class_methods\(\s*)(.+)(\s*\))(.*)/m) do
|
87
|
+
"#{blank($1)}#{$2}#{blank($3)}#{$4}"
|
88
|
+
end
|
67
89
|
end
|
90
|
+
end
|
68
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
|
+
|
103
|
+
def on_method_add_arg(call, arg_paren)
|
69
104
|
# T.must(foo)
|
70
105
|
# T.reveal_type(foo)
|
71
|
-
|
106
|
+
# T.unsafe(foo)
|
107
|
+
if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident (?:must|reveal_type|unsafe)>>/) &&
|
72
108
|
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
73
|
-
patterns << TOneArgMethodCallParensPattern.new(call.range.begin
|
109
|
+
patterns << TOneArgMethodCallParensPattern.new(call.range.begin...arg_paren.range.end)
|
74
110
|
end
|
75
111
|
|
76
112
|
# T.assert_type!(foo, bar)
|
77
113
|
# T.cast(foo, bar)
|
78
114
|
# T.let(foo, bar)
|
79
|
-
if call.match?(
|
115
|
+
if call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>\z/) &&
|
80
116
|
arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
|
81
|
-
patterns <<
|
117
|
+
patterns <<
|
118
|
+
TTwoArgMethodCallParensPattern.new(
|
119
|
+
call.range.begin...arg_paren.range.end,
|
120
|
+
comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
|
121
|
+
)
|
82
122
|
end
|
83
123
|
|
84
124
|
# T.bind(self, foo)
|
85
125
|
if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident bind>>/) &&
|
86
126
|
arg_paren.match?(/<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>/)
|
87
|
-
patterns <<
|
127
|
+
patterns <<
|
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
|
+
)
|
88
132
|
end
|
89
133
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
134
|
+
# abstract!
|
135
|
+
# final!
|
136
|
+
# interface!
|
137
|
+
if call.match?(/<fcall <@ident (?:abstract|final|interface)!>>/) &&
|
138
|
+
arg_paren.match?("<args >")
|
139
|
+
patterns << DeclarationPattern.new(call.range.begin...arg_paren.range.end)
|
100
140
|
end
|
101
|
-
end
|
102
141
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
patterns << TTypeAliasBraceBlockPattern.new(method_add_arg.range.begin..block.range.end)
|
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>>/)
|
145
|
+
patterns << MixesInClassMethodsPattern.new(call.range.begin...arg_paren.range.end)
|
108
146
|
end
|
109
147
|
|
110
148
|
super
|
111
149
|
end
|
112
150
|
|
113
|
-
#
|
114
|
-
|
151
|
+
# prop :foo, String
|
152
|
+
# const :foo, String
|
153
|
+
class PropPattern < Pattern
|
115
154
|
def replace(segment)
|
116
|
-
segment.gsub(/(
|
117
|
-
"#{
|
155
|
+
segment.gsub(/((?:prop|const)\s+:.+),(\s*)([^\n;,]+)(.*)/m) do
|
156
|
+
"#{$1} #{$2}#{blank($3)}#{$4}"
|
118
157
|
end
|
119
158
|
end
|
120
159
|
end
|
121
160
|
|
122
161
|
def on_command(ident, args_add_block)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
162
|
+
if ident.match?(/<@ident (?:const|prop)>/)
|
163
|
+
# prop :foo, String
|
164
|
+
# const :foo, String
|
165
|
+
if args_add_block.match?(/<args_add_block <args <symbol_literal <symbol <@ident .+>>> <.+> false>/)
|
166
|
+
patterns << PropPattern.new(ident.range.begin...args_add_block.range.end)
|
167
|
+
end
|
127
168
|
end
|
128
169
|
|
129
170
|
super
|
130
171
|
end
|
131
172
|
|
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
173
|
def on_command_call(var_ref, period, ident, args_add_block)
|
142
174
|
if var_ref.match?("<var_ref <@const T>>") && period.match?("<@period .>")
|
143
175
|
# T.must foo
|
144
|
-
|
176
|
+
# T.reveal_type foo
|
177
|
+
# T.unsafe foo
|
178
|
+
if ident.match?(/<@ident (?:must|reveal_type|unsafe)>/) &&
|
145
179
|
args_add_block.match?(/<args_add_block <args <.+>> false>/) &&
|
146
180
|
args_add_block.body[0].body.length == 1
|
147
|
-
patterns << TMustNoParensPattern.new(var_ref.range.begin
|
181
|
+
patterns << TMustNoParensPattern.new(var_ref.range.begin...args_add_block.range.end)
|
148
182
|
end
|
149
183
|
end
|
150
184
|
|
@@ -154,8 +188,8 @@ module Sorbet
|
|
154
188
|
# sig { foo } =>
|
155
189
|
class SigBracesPattern < Pattern
|
156
190
|
def replace(segment)
|
157
|
-
segment.gsub(/(sig\s*\{.+\})(.*)/) do
|
158
|
-
"#{
|
191
|
+
segment.gsub(/(sig\s*\{.+\})(.*)/m) do
|
192
|
+
"#{blank($1)}#{$2}"
|
159
193
|
end
|
160
194
|
end
|
161
195
|
end
|
data/lib/sorbet/eraser.rb
CHANGED
@@ -5,17 +5,10 @@ require "ripper"
|
|
5
5
|
require "sorbet/eraser/parser"
|
6
6
|
require "sorbet/eraser/patterns"
|
7
7
|
require "sorbet/eraser/version"
|
8
|
+
require "t"
|
8
9
|
|
9
10
|
module Sorbet
|
10
11
|
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
12
|
# Hook the patterns into the parser so that the correct methods get
|
20
13
|
# overridden and will trigger replacements.
|
21
14
|
Parser.prepend(Patterns)
|
data/lib/t/enum.rb
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module T
|
4
|
+
# This is mostly copy-pasted from sorbet-runtime since we have to maintain the
|
5
|
+
# same behavior.
|
6
|
+
class T::Enum
|
7
|
+
## Enum class methods ##
|
8
|
+
|
9
|
+
def self.values
|
10
|
+
if @values.nil?
|
11
|
+
raise "Attempting to access values of #{self.class} before it has been initialized." \
|
12
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
13
|
+
end
|
14
|
+
@values
|
15
|
+
end
|
16
|
+
|
17
|
+
# This exists for compatibility with the interface of `Hash` & mostly to support
|
18
|
+
# the HashEachMethods Rubocop.
|
19
|
+
def self.each_value(&blk)
|
20
|
+
if blk
|
21
|
+
values.each(&blk)
|
22
|
+
else
|
23
|
+
values.each
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Convert from serialized value to enum instance
|
28
|
+
def self.try_deserialize(serialized_val)
|
29
|
+
if @mapping.nil?
|
30
|
+
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
|
31
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
32
|
+
end
|
33
|
+
@mapping[serialized_val]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert from serialized value to enum instance.
|
37
|
+
#
|
38
|
+
# @return [self]
|
39
|
+
# @raise [KeyError] if serialized value does not match any instance.
|
40
|
+
def self.from_serialized(serialized_val)
|
41
|
+
res = try_deserialize(serialized_val)
|
42
|
+
if res.nil?
|
43
|
+
raise KeyError.new("Enum #{self} key not found: #{serialized_val.inspect}")
|
44
|
+
end
|
45
|
+
res
|
46
|
+
end
|
47
|
+
|
48
|
+
# Note: It would have been nice to make this method final before people started overriding it.
|
49
|
+
# @return [Boolean] Does the given serialized value correspond with any of this enum's values.
|
50
|
+
def self.has_serialized?(serialized_val)
|
51
|
+
if @mapping.nil?
|
52
|
+
raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
|
53
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
54
|
+
end
|
55
|
+
@mapping.include?(serialized_val)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.serialize(instance)
|
59
|
+
return nil if instance.nil?
|
60
|
+
|
61
|
+
if self == T::Enum
|
62
|
+
raise "Cannot call T::Enum.serialize directly. You must call on a specific child class."
|
63
|
+
end
|
64
|
+
if instance.class != self
|
65
|
+
raise "Cannot call #serialize on a value that is not an instance of #{self}."
|
66
|
+
end
|
67
|
+
instance.serialize
|
68
|
+
end
|
69
|
+
|
70
|
+
# Note: Failed CriticalMethodsNoRuntimeTypingTest
|
71
|
+
def self.deserialize(mongo_value)
|
72
|
+
if self == T::Enum
|
73
|
+
raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
|
74
|
+
end
|
75
|
+
self.from_serialized(mongo_value)
|
76
|
+
end
|
77
|
+
|
78
|
+
## Enum instance methods ##
|
79
|
+
|
80
|
+
def dup
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def clone
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def serialize
|
89
|
+
assert_bound!
|
90
|
+
@serialized_val
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_json(*args)
|
94
|
+
serialize.to_json(*args)
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
inspect
|
99
|
+
end
|
100
|
+
|
101
|
+
def inspect
|
102
|
+
"#<#{self.class.name}::#{@const_name || '__UNINITIALIZED__'}>"
|
103
|
+
end
|
104
|
+
|
105
|
+
def <=>(other)
|
106
|
+
case other
|
107
|
+
when self.class
|
108
|
+
self.serialize <=> other.serialize
|
109
|
+
else
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# NB: Do not call this method. This exists to allow for a safe migration path in places where enum
|
115
|
+
# values are compared directly against string values.
|
116
|
+
#
|
117
|
+
# Ruby's string has a weird quirk where `'my_string' == obj` calls obj.==('my_string') if obj
|
118
|
+
# responds to the `to_str` method. It does not actually call `to_str` however.
|
119
|
+
#
|
120
|
+
# See https://ruby-doc.org/core-2.4.0/String.html#method-i-3D-3D
|
121
|
+
def to_str
|
122
|
+
msg = 'Implicit conversion of Enum instances to strings is not allowed. Call #serialize instead.'
|
123
|
+
raise NoMethodError.new(msg)
|
124
|
+
end
|
125
|
+
|
126
|
+
def ==(other)
|
127
|
+
case other
|
128
|
+
when String
|
129
|
+
false
|
130
|
+
else
|
131
|
+
super(other)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def ===(other)
|
136
|
+
case other
|
137
|
+
when String
|
138
|
+
false
|
139
|
+
else
|
140
|
+
super(other)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
## Private implementation ##
|
145
|
+
|
146
|
+
def initialize(serialized_val=nil)
|
147
|
+
raise 'T::Enum is abstract' if self.class == T::Enum
|
148
|
+
if !self.class.started_initializing?
|
149
|
+
raise "Must instantiate all enum values of #{self.class} inside 'enums do'."
|
150
|
+
end
|
151
|
+
if self.class.fully_initialized?
|
152
|
+
raise "Cannot instantiate a new enum value of #{self.class} after it has been initialized."
|
153
|
+
end
|
154
|
+
|
155
|
+
serialized_val = serialized_val.frozen? ? serialized_val : serialized_val.dup.freeze
|
156
|
+
@serialized_val = serialized_val
|
157
|
+
@const_name = nil
|
158
|
+
self.class._register_instance(self)
|
159
|
+
end
|
160
|
+
|
161
|
+
private def assert_bound!
|
162
|
+
if @const_name.nil?
|
163
|
+
raise "Attempting to access Enum value on #{self.class} before it has been initialized." \
|
164
|
+
" Enums are not initialized until the 'enums do' block they are defined in has finished running."
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def _bind_name(const_name)
|
169
|
+
@const_name = const_name
|
170
|
+
@serialized_val = const_to_serialized_val(const_name) if @serialized_val.nil?
|
171
|
+
freeze
|
172
|
+
end
|
173
|
+
|
174
|
+
private def const_to_serialized_val(const_name)
|
175
|
+
# Historical note: We convert to lowercase names because the majority of existing calls to
|
176
|
+
# `make_accessible` were arrays of lowercase strings. Doing this conversion allowed for the
|
177
|
+
# least amount of repetition in migrated declarations.
|
178
|
+
const_name.to_s.downcase.freeze
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.started_initializing?
|
182
|
+
@started_initializing ||= false
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.fully_initialized?
|
186
|
+
@fully_initialized ||= false
|
187
|
+
end
|
188
|
+
|
189
|
+
# Maintains the order in which values are defined
|
190
|
+
def self._register_instance(instance)
|
191
|
+
@values ||= []
|
192
|
+
@values << instance
|
193
|
+
end
|
194
|
+
|
195
|
+
# Entrypoint for allowing people to register new enum values.
|
196
|
+
# All enum values must be defined within this block.
|
197
|
+
def self.enums(&blk)
|
198
|
+
raise "enums cannot be defined for T::Enum" if self == T::Enum
|
199
|
+
raise "Enum #{self} was already initialized" if @fully_initialized
|
200
|
+
raise "Enum #{self} is still initializing" if @started_initializing
|
201
|
+
|
202
|
+
@started_initializing = true
|
203
|
+
|
204
|
+
@values = nil
|
205
|
+
|
206
|
+
yield
|
207
|
+
|
208
|
+
@mapping = nil
|
209
|
+
@mapping = {}
|
210
|
+
|
211
|
+
# Freeze the Enum class and bind the constant names into each of the instances.
|
212
|
+
self.constants(false).each do |const_name|
|
213
|
+
instance = self.const_get(const_name, false)
|
214
|
+
if !instance.is_a?(self)
|
215
|
+
raise "Invalid constant #{self}::#{const_name} on enum. " \
|
216
|
+
"All constants defined for an enum must be instances itself (e.g. `Foo = new`)."
|
217
|
+
end
|
218
|
+
|
219
|
+
instance._bind_name(const_name)
|
220
|
+
serialized = instance.serialize
|
221
|
+
if @mapping.include?(serialized)
|
222
|
+
raise "Enum values must have unique serializations. Value '#{serialized}' is repeated on #{self}."
|
223
|
+
end
|
224
|
+
@mapping[serialized] = instance
|
225
|
+
end
|
226
|
+
@values.freeze
|
227
|
+
@mapping.freeze
|
228
|
+
|
229
|
+
orphaned_instances = @values - @mapping.values
|
230
|
+
if !orphaned_instances.empty?
|
231
|
+
raise "Enum values must be assigned to constants: #{orphaned_instances.map {|v| v.instance_variable_get('@serialized_val')}}"
|
232
|
+
end
|
233
|
+
|
234
|
+
@fully_initialized = true
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.inherited(child_class)
|
238
|
+
super
|
239
|
+
|
240
|
+
raise "Inheriting from children of T::Enum is prohibited" if self != T::Enum
|
241
|
+
end
|
242
|
+
|
243
|
+
# Marshal support
|
244
|
+
def _dump(_level)
|
245
|
+
Marshal.dump(serialize)
|
246
|
+
end
|
247
|
+
|
248
|
+
def self._load(args)
|
249
|
+
deserialize(Marshal.load(args))
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
data/lib/t/props.rb
ADDED
@@ -0,0 +1,59 @@
|
|
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)
|
24
|
+
attr_accessor name
|
25
|
+
end
|
26
|
+
|
27
|
+
def const(name, rules = {})
|
28
|
+
create_prop(name)
|
29
|
+
attr_reader name
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def create_prop(name)
|
35
|
+
props << name
|
36
|
+
props.sort!
|
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
|
+
if self.class.props == hash.keys.sort
|
44
|
+
hash.each { |key, value| instance_variable_set("@#{key}", value) }
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Expected keys #{self.class.props} but got #{hash.keys.sort}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# This module is entirely empty because we haven't implemented anything from
|
51
|
+
# sorbet-runtime here.
|
52
|
+
module Serializable
|
53
|
+
end
|
54
|
+
|
55
|
+
# This is empty for the same reason.
|
56
|
+
module Constructor
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/t/struct.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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
|
+
|
12
|
+
# This is a shim for the T::Struct class because since you're actually
|
13
|
+
# inheriting from the class there's not really a way to remove it from the
|
14
|
+
# source.
|
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
|
51
|
+
end
|
52
|
+
end
|
data/lib/t.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "t/enum"
|
4
|
+
require "t/props"
|
5
|
+
require "t/struct"
|
6
|
+
|
7
|
+
# For some constructs, it doesn't make as much sense to entirely remove them
|
8
|
+
# since they're actually used to change runtime behavior. For example, T.absurd
|
9
|
+
# will always raise an error. In this case instead of removing the content, we
|
10
|
+
# can just shim it.
|
11
|
+
module T
|
12
|
+
# These methods should really not be being called in a loop or any other kind
|
13
|
+
# of hot path, so here we're just going to shim them.
|
14
|
+
module Helpers
|
15
|
+
def abstract!; end
|
16
|
+
def interface!; end
|
17
|
+
def final!; end
|
18
|
+
def sealed!; end
|
19
|
+
def mixes_in_class_methods(*); end
|
20
|
+
def requires_ancestor(*); end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Similar to the Helpers module, these things should only be called a couple
|
24
|
+
# of times, so shimming them here.
|
25
|
+
module Generic
|
26
|
+
include Helpers
|
27
|
+
def type_member(*, **); end
|
28
|
+
def type_template(*, **); end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Keeping this module as a thing so that if there's any kind of weird
|
32
|
+
# reflection going on like is_a?(T::Sig) it will still work.
|
33
|
+
module Sig
|
34
|
+
end
|
35
|
+
|
36
|
+
# Type aliases don't actually do anything, but they are usually assigned to
|
37
|
+
# constants, so in that case we need to return something.
|
38
|
+
def self.type_alias
|
39
|
+
Object.new
|
40
|
+
end
|
41
|
+
|
42
|
+
# Absurd always raises a TypeError within Sorbet, so mirroring that behavior
|
43
|
+
# here when T.absurd is called.
|
44
|
+
def self.absurd(value)
|
45
|
+
raise TypeError, value
|
46
|
+
end
|
47
|
+
end
|
data/sorbet-eraser.gemspec
CHANGED
@@ -1,22 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative "lib/sorbet/eraser/version"
|
4
|
+
|
5
|
+
version = Sorbet::Eraser::VERSION
|
6
|
+
repository = "https://github.com/kddnewton/sorbet-eraser"
|
6
7
|
|
7
8
|
Gem::Specification.new do |spec|
|
8
9
|
spec.name = "sorbet-eraser"
|
9
|
-
spec.version =
|
10
|
+
spec.version = version
|
10
11
|
spec.authors = ["Kevin Newton"]
|
11
12
|
spec.email = ["kddnewton@gmail.com"]
|
12
13
|
|
13
14
|
spec.summary = "Erase all traces of sorbet-runtime code."
|
14
|
-
spec.homepage =
|
15
|
+
spec.homepage = repository
|
15
16
|
spec.license = "MIT"
|
16
17
|
|
17
|
-
spec.
|
18
|
+
spec.metadata = {
|
19
|
+
"bug_tracker_uri" => "#{repository}/issues",
|
20
|
+
"changelog_uri" => "#{repository}/blob/v#{version}/CHANGELOG.md",
|
21
|
+
"source_code_uri" => repository,
|
22
|
+
"rubygems_mfa_required" => "true"
|
23
|
+
}
|
24
|
+
|
25
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
26
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
27
|
end
|
28
|
+
|
20
29
|
spec.bindir = "exe"
|
21
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
31
|
spec.require_paths = ["lib"]
|
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.2.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
|
@@ -55,11 +55,15 @@ dependencies:
|
|
55
55
|
description:
|
56
56
|
email:
|
57
57
|
- kddnewton@gmail.com
|
58
|
-
executables:
|
58
|
+
executables:
|
59
|
+
- sorbet-eraser
|
59
60
|
extensions: []
|
60
61
|
extra_rdoc_files: []
|
61
62
|
files:
|
63
|
+
- ".github/dependabot.yml"
|
64
|
+
- ".github/workflows/main.yml"
|
62
65
|
- ".gitignore"
|
66
|
+
- CHANGELOG.md
|
63
67
|
- Gemfile
|
64
68
|
- Gemfile.lock
|
65
69
|
- LICENSE
|
@@ -67,16 +71,26 @@ files:
|
|
67
71
|
- Rakefile
|
68
72
|
- bin/console
|
69
73
|
- bin/setup
|
74
|
+
- exe/sorbet-eraser
|
70
75
|
- lib/sorbet/eraser.rb
|
71
76
|
- lib/sorbet/eraser/autoload.rb
|
77
|
+
- lib/sorbet/eraser/cli.rb
|
72
78
|
- lib/sorbet/eraser/parser.rb
|
73
79
|
- lib/sorbet/eraser/patterns.rb
|
74
80
|
- lib/sorbet/eraser/version.rb
|
81
|
+
- lib/t.rb
|
82
|
+
- lib/t/enum.rb
|
83
|
+
- lib/t/props.rb
|
84
|
+
- lib/t/struct.rb
|
75
85
|
- sorbet-eraser.gemspec
|
76
86
|
homepage: https://github.com/kddnewton/sorbet-eraser
|
77
87
|
licenses:
|
78
88
|
- MIT
|
79
|
-
metadata:
|
89
|
+
metadata:
|
90
|
+
bug_tracker_uri: https://github.com/kddnewton/sorbet-eraser/issues
|
91
|
+
changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.2.0/CHANGELOG.md
|
92
|
+
source_code_uri: https://github.com/kddnewton/sorbet-eraser
|
93
|
+
rubygems_mfa_required: 'true'
|
80
94
|
post_install_message:
|
81
95
|
rdoc_options: []
|
82
96
|
require_paths:
|
@@ -92,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
106
|
- !ruby/object:Gem::Version
|
93
107
|
version: '0'
|
94
108
|
requirements: []
|
95
|
-
rubygems_version: 3.
|
109
|
+
rubygems_version: 3.4.1
|
96
110
|
signing_key:
|
97
111
|
specification_version: 4
|
98
112
|
summary: Erase all traces of sorbet-runtime code.
|