sorbet-eraser 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: