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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3859ee512cb01ed5b84df15af3dedaa629c30d122e80a7436821841f87184562
4
- data.tar.gz: 8392610b8fb03ea78d2572b684c2ccf9b4806f082043e3b2614550a370eeea13
3
+ metadata.gz: 420576226d037471f75759599ea53397cd251dd546431b24e9d3cf556285902a
4
+ data.tar.gz: 7c6d09a382019245254f55d0a9eff1b4cd2430d066c364efde59567ada2f56c0
5
5
  SHA512:
6
- metadata.gz: 02ee32fc8bcf3d1951ddd9577a4f46c181a01f196eb6c2af34bb8bb0b68a0ebc3fb28515d0ca1e391d2d1ee305d3012aafd943918df643dd2a3a44c5272a38b2
7
- data.tar.gz: fa7b5e80e3fda49d50df015532110f6b4050450c5348f4d9f0ac6f40c8e2b411882a5348b38e305419b74e0bf6e833b887997bb6757e440fb3acc552e286d7e4
6
+ metadata.gz: 5478636318a06fe00d279813bd724659d6653efef7364ea583e11566e455cf9528e8c14338dfdab11298e329225ca9ef2c0847df4125b0edea09f4ca5a953452
7
+ data.tar.gz: 26d79c3b183ef5f7a629205b94e61a09862db83ca50884d68e1e7d22954b1543355787e7fd798223ff8b483bdbdcbb5c14a49959d72f19a12bfcedd13f8f51e8
data/.gitignore CHANGED
@@ -6,3 +6,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ test.rb
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.0...HEAD
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sorbet-eraser (0.4.0)
4
+ sorbet-eraser (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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` | ✅ | `# typed: ignore` |
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` |
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
- # 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
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
- Bootsnap::CompileCache::ISeq.singleton_class.prepend(Sorbet::Eraser::Patch)
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
@@ -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
- new(argv.flat_map { |pattern| Dir.glob(pattern) }).start
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
- File.write(filepath, Eraser.erase(File.read(filepath)))
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
@@ -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[arg.range.end..].index("]") + 1
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[..arg.range.begin].rindex("(")
147
- rend = arg.range.end + source[arg.range.end..].index(")") + 1
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[..loc]
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[..loc]
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[...arg.range.begin].rindex(LISTS.fetch(arg.event))
166
- rend = source[arg.range.end..].index(TERMINATORS.fetch(source[rbegin + 2]) { source[rbegin + 2] }) + arg.range.end + 1
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[...(params || body).range.begin].rindex("{")
176
+ rbegin = source.byteslice(...(params || body).range.begin).rindex("{")
177
177
 
178
178
  rend = body.range&.end || params.range.end
179
- rend = rend + source[rend..].index("}") + 1
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[..loc]
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[...(params || body).range.begin].rindex("do")
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[rend..].index("end") + 3
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[..loc]
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[..loc]
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
- original = source[range]
17
- replaced = replace(original)
16
+ encoding = source.encoding
17
+ source.force_encoding(Encoding::ASCII_8BIT)
18
18
 
19
- # puts "Replacing #{original} (len=#{original.length}) " \
20
- # "with #{replaced} (len=#{replaced.length})"
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..comma, (comma + 1)..-1
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*)(.+)(\s*,)(.*)/m) do
64
- "#{blank($1)}#{$2}#{blank($3)}#{$4}"
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: strong
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(/(\A#\s*typed:\s*)(?:ignore|false|true|strong)(\s*)\z/) do
110
- "#{$1}ignore#{$2}"
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sorbet
4
4
  module Eraser
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  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.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-03 00:00:00.000000000 Z
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.4.0/CHANGELOG.md
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: