syntax_tree 6.0.2 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +6 -6
- data/README.md +29 -0
- data/lib/syntax_tree/cli.rb +91 -0
- data/lib/syntax_tree/index.rb +233 -23
- data/lib/syntax_tree/node.rb +65 -32
- data/lib/syntax_tree/parser.rb +8 -1
- data/lib/syntax_tree/reflection.rb +20 -5
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/yarv/compiler.rb +1 -2
- data/lib/syntax_tree/yarv/instruction_sequence.rb +14 -3
- data/lib/syntax_tree/yarv/instructions.rb +58 -0
- data/lib/syntax_tree.rb +1 -0
- data/tasks/sorbet.rake +96 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7515a827509d352259e6cae476930093fe382fcc35c248fba3ab716019e69ca9
|
4
|
+
data.tar.gz: add2625387abe5616f90d28843f4fcebac5b68222f9a6258cbfb04fbd41b4772
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea0a666bd65180facaab89acd7b7bd87d3f5208c69a0fe80b91d1d25d57cdc2c281ac301070da8d73c7d119574a7f7de30c0facb0d7131f895bbc8fa7fe582c5
|
7
|
+
data.tar.gz: 4c9df489dcb876280a219a8e213a800f0544a0aaa02a303d8fc3ba948e07e195dbfb21da8f8f04825abc4bf61fb9ec92141d5a00cc7b3d53b1d6103d58d10be3
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -7,7 +7,7 @@ AllCops:
|
|
7
7
|
SuggestExtensions: false
|
8
8
|
TargetRubyVersion: 2.7
|
9
9
|
Exclude:
|
10
|
-
- '{.git,.github,bin,coverage,pkg,spec,test/fixtures,vendor,tmp}/**/*'
|
10
|
+
- '{.git,.github,bin,coverage,pkg,sorbet,spec,test/fixtures,vendor,tmp}/**/*'
|
11
11
|
- test.rb
|
12
12
|
|
13
13
|
Gemspec/DevelopmentDependencies:
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.0
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [6.1.0] - 2023-03-20
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- The `stree ctags` command for generating ctags like `universal-ctags` or `ripper-tags` would.
|
14
|
+
- The `definedivar` YARV instruction has been added to match CRuby's implementation.
|
15
|
+
- We now generate better Sorbet RBI files for the nodes in the tree and the visitors.
|
16
|
+
- `SyntaxTree::Reflection.nodes` now includes the visitor method.
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- We now explicitly require `pp` in environments that need it.
|
21
|
+
|
9
22
|
## [6.0.2] - 2023-03-03
|
10
23
|
|
11
24
|
### Added
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
syntax_tree (6.0
|
4
|
+
syntax_tree (6.1.0)
|
5
5
|
prettier_print (>= 1.2.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -10,16 +10,16 @@ GEM
|
|
10
10
|
ast (2.4.2)
|
11
11
|
docile (1.4.0)
|
12
12
|
json (2.6.3)
|
13
|
-
minitest (5.
|
13
|
+
minitest (5.18.0)
|
14
14
|
parallel (1.22.1)
|
15
|
-
parser (3.2.1.
|
15
|
+
parser (3.2.1.1)
|
16
16
|
ast (~> 2.4.1)
|
17
|
-
prettier_print (1.2.
|
17
|
+
prettier_print (1.2.1)
|
18
18
|
rainbow (3.1.1)
|
19
19
|
rake (13.0.6)
|
20
20
|
regexp_parser (2.7.0)
|
21
21
|
rexml (3.2.5)
|
22
|
-
rubocop (1.
|
22
|
+
rubocop (1.48.1)
|
23
23
|
json (~> 2.3)
|
24
24
|
parallel (~> 1.10)
|
25
25
|
parser (>= 3.2.0.0)
|
@@ -31,7 +31,7 @@ GEM
|
|
31
31
|
unicode-display_width (>= 2.4.0, < 3.0)
|
32
32
|
rubocop-ast (1.27.0)
|
33
33
|
parser (>= 3.2.1.0)
|
34
|
-
ruby-progressbar (1.
|
34
|
+
ruby-progressbar (1.13.0)
|
35
35
|
simplecov (0.22.0)
|
36
36
|
docile (~> 1.1)
|
37
37
|
simplecov-html (~> 0.11)
|
data/README.md
CHANGED
@@ -15,6 +15,7 @@ It is built with only standard library dependencies. It additionally ships with
|
|
15
15
|
- [CLI](#cli)
|
16
16
|
- [ast](#ast)
|
17
17
|
- [check](#check)
|
18
|
+
- [ctags](#ctags)
|
18
19
|
- [expr](#expr)
|
19
20
|
- [format](#format)
|
20
21
|
- [json](#json)
|
@@ -139,6 +140,33 @@ To change the print width that you are checking against, specify the `--print-wi
|
|
139
140
|
stree check --print-width=100 path/to/file.rb
|
140
141
|
```
|
141
142
|
|
143
|
+
### ctags
|
144
|
+
|
145
|
+
This command will output to stdout a set of tags suitable for usage with [ctags](https://github.com/universal-ctags/ctags).
|
146
|
+
|
147
|
+
```sh
|
148
|
+
stree ctags path/to/file.rb
|
149
|
+
```
|
150
|
+
|
151
|
+
For a file containing the following Ruby code:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
class Foo
|
155
|
+
end
|
156
|
+
|
157
|
+
class Bar < Foo
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
you will receive:
|
162
|
+
|
163
|
+
```
|
164
|
+
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
165
|
+
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
166
|
+
Bar test.rb /^class Bar < Foo$/;" c inherits:Foo
|
167
|
+
Foo test.rb /^class Foo$/;" c
|
168
|
+
```
|
169
|
+
|
142
170
|
### expr
|
143
171
|
|
144
172
|
This command will output a Ruby case-match expression that would match correctly against the first expression of the input.
|
@@ -788,6 +816,7 @@ inherit_gem:
|
|
788
816
|
* [Neovim](https://neovim.io/) - [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig).
|
789
817
|
* [Vim](https://www.vim.org/) - [dense-analysis/ale](https://github.com/dense-analysis/ale).
|
790
818
|
* [VSCode](https://code.visualstudio.com/) - [ruby-syntax-tree/vscode-syntax-tree](https://github.com/ruby-syntax-tree/vscode-syntax-tree).
|
819
|
+
* [Emacs](https://www.gnu.org/software/emacs/) - [emacs-format-all-the-code](https://github.com/lassik/emacs-format-all-the-code).
|
791
820
|
|
792
821
|
## Contributing
|
793
822
|
|
data/lib/syntax_tree/cli.rb
CHANGED
@@ -154,6 +154,92 @@ module SyntaxTree
|
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
157
|
+
# An action of the CLI that generates ctags for the given source.
|
158
|
+
class CTags < Action
|
159
|
+
attr_reader :entries
|
160
|
+
|
161
|
+
def initialize(options)
|
162
|
+
super(options)
|
163
|
+
@entries = []
|
164
|
+
end
|
165
|
+
|
166
|
+
def run(item)
|
167
|
+
lines = item.source.lines(chomp: true)
|
168
|
+
|
169
|
+
SyntaxTree
|
170
|
+
.index(item.source)
|
171
|
+
.each do |entry|
|
172
|
+
line = lines[entry.location.line - 1]
|
173
|
+
pattern = "/^#{line.gsub("\\", "\\\\\\\\").gsub("/", "\\/")}$/;\""
|
174
|
+
|
175
|
+
entries << case entry
|
176
|
+
when SyntaxTree::Index::ModuleDefinition
|
177
|
+
parts = [entry.name, item.filepath, pattern, "m"]
|
178
|
+
|
179
|
+
if entry.nesting != [[entry.name]]
|
180
|
+
parts << "class:#{entry.nesting.flatten.tap(&:pop).join(".")}"
|
181
|
+
end
|
182
|
+
|
183
|
+
parts.join("\t")
|
184
|
+
when SyntaxTree::Index::ClassDefinition
|
185
|
+
parts = [entry.name, item.filepath, pattern, "c"]
|
186
|
+
|
187
|
+
if entry.nesting != [[entry.name]]
|
188
|
+
parts << "class:#{entry.nesting.flatten.tap(&:pop).join(".")}"
|
189
|
+
end
|
190
|
+
|
191
|
+
unless entry.superclass.empty?
|
192
|
+
inherits = entry.superclass.join(".").delete_prefix(".")
|
193
|
+
parts << "inherits:#{inherits}"
|
194
|
+
end
|
195
|
+
|
196
|
+
parts.join("\t")
|
197
|
+
when SyntaxTree::Index::MethodDefinition
|
198
|
+
parts = [entry.name, item.filepath, pattern, "f"]
|
199
|
+
|
200
|
+
unless entry.nesting.empty?
|
201
|
+
parts << "class:#{entry.nesting.flatten.join(".")}"
|
202
|
+
end
|
203
|
+
|
204
|
+
parts.join("\t")
|
205
|
+
when SyntaxTree::Index::SingletonMethodDefinition
|
206
|
+
parts = [entry.name, item.filepath, pattern, "F"]
|
207
|
+
|
208
|
+
unless entry.nesting.empty?
|
209
|
+
parts << "class:#{entry.nesting.flatten.join(".")}"
|
210
|
+
end
|
211
|
+
|
212
|
+
parts.join("\t")
|
213
|
+
when SyntaxTree::Index::AliasMethodDefinition
|
214
|
+
parts = [entry.name, item.filepath, pattern, "a"]
|
215
|
+
|
216
|
+
unless entry.nesting.empty?
|
217
|
+
parts << "class:#{entry.nesting.flatten.join(".")}"
|
218
|
+
end
|
219
|
+
|
220
|
+
parts.join("\t")
|
221
|
+
when SyntaxTree::Index::ConstantDefinition
|
222
|
+
parts = [entry.name, item.filepath, pattern, "C"]
|
223
|
+
|
224
|
+
unless entry.nesting.empty?
|
225
|
+
parts << "class:#{entry.nesting.flatten.join(".")}"
|
226
|
+
end
|
227
|
+
|
228
|
+
parts.join("\t")
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def success
|
234
|
+
puts(<<~HEADER)
|
235
|
+
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
236
|
+
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
237
|
+
HEADER
|
238
|
+
|
239
|
+
entries.sort.each { |entry| puts(entry) }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
157
243
|
# An action of the CLI that formats the source twice to check if the first
|
158
244
|
# format is not idempotent.
|
159
245
|
class Debug < Action
|
@@ -327,6 +413,9 @@ module SyntaxTree
|
|
327
413
|
#{Color.bold("stree check [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
|
328
414
|
Check that the given files are formatted as syntax tree would format them
|
329
415
|
|
416
|
+
#{Color.bold("stree ctags [-e SCRIPT] FILE")}
|
417
|
+
Print out a ctags-compatible index of the given files
|
418
|
+
|
330
419
|
#{Color.bold("stree debug [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
|
331
420
|
Check that the given files can be formatted idempotently
|
332
421
|
|
@@ -488,6 +577,8 @@ module SyntaxTree
|
|
488
577
|
AST.new(options)
|
489
578
|
when "c", "check"
|
490
579
|
Check.new(options)
|
580
|
+
when "ctags"
|
581
|
+
CTags.new(options)
|
491
582
|
when "debug"
|
492
583
|
Debug.new(options)
|
493
584
|
when "doc"
|
data/lib/syntax_tree/index.rb
CHANGED
@@ -31,6 +31,18 @@ module SyntaxTree
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
# This entry represents a constant assignment.
|
35
|
+
class ConstantDefinition
|
36
|
+
attr_reader :nesting, :name, :location, :comments
|
37
|
+
|
38
|
+
def initialize(nesting, name, location, comments)
|
39
|
+
@nesting = nesting
|
40
|
+
@name = name
|
41
|
+
@location = location
|
42
|
+
@comments = comments
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
34
46
|
# This entry represents a module definition using the module keyword.
|
35
47
|
class ModuleDefinition
|
36
48
|
attr_reader :nesting, :name, :location, :comments
|
@@ -68,6 +80,19 @@ module SyntaxTree
|
|
68
80
|
end
|
69
81
|
end
|
70
82
|
|
83
|
+
# This entry represents a method definition that was created using the alias
|
84
|
+
# keyword.
|
85
|
+
class AliasMethodDefinition
|
86
|
+
attr_reader :nesting, :name, :location, :comments
|
87
|
+
|
88
|
+
def initialize(nesting, name, location, comments)
|
89
|
+
@nesting = nesting
|
90
|
+
@name = name
|
91
|
+
@location = location
|
92
|
+
@comments = comments
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
71
96
|
# When you're using the instruction sequence backend, this class is used to
|
72
97
|
# lazily parse comments out of the source code.
|
73
98
|
class FileComments
|
@@ -178,7 +203,14 @@ module SyntaxTree
|
|
178
203
|
end
|
179
204
|
|
180
205
|
def find_constant_path(insns, index)
|
181
|
-
index -= 1 while
|
206
|
+
index -= 1 while index >= 0 &&
|
207
|
+
(
|
208
|
+
insns[index].is_a?(Integer) ||
|
209
|
+
(
|
210
|
+
insns[index].is_a?(Array) &&
|
211
|
+
%i[swap topn].include?(insns[index][0])
|
212
|
+
)
|
213
|
+
)
|
182
214
|
insn = insns[index]
|
183
215
|
|
184
216
|
if insn.is_a?(Array) && insn[0] == :opt_getconstant_path
|
@@ -207,11 +239,43 @@ module SyntaxTree
|
|
207
239
|
end
|
208
240
|
end
|
209
241
|
|
242
|
+
def find_attr_arguments(insns, index)
|
243
|
+
orig_argc = insns[index][1][:orig_argc]
|
244
|
+
names = []
|
245
|
+
|
246
|
+
current = index - 1
|
247
|
+
while current >= 0 && names.length < orig_argc
|
248
|
+
if insns[current].is_a?(Array) && insns[current][0] == :putobject
|
249
|
+
names.unshift(insns[current][1])
|
250
|
+
end
|
251
|
+
|
252
|
+
current -= 1
|
253
|
+
end
|
254
|
+
|
255
|
+
names if insns[current] == [:putself] && names.length == orig_argc
|
256
|
+
end
|
257
|
+
|
258
|
+
def method_definition(nesting, name, location, file_comments)
|
259
|
+
comments = EntryComments.new(file_comments, location)
|
260
|
+
|
261
|
+
if nesting.last == [:singletonclass]
|
262
|
+
SingletonMethodDefinition.new(
|
263
|
+
nesting[0...-1],
|
264
|
+
name,
|
265
|
+
location,
|
266
|
+
comments
|
267
|
+
)
|
268
|
+
else
|
269
|
+
MethodDefinition.new(nesting, name, location, comments)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
210
273
|
def index_iseq(iseq, file_comments)
|
211
274
|
results = []
|
212
275
|
queue = [[iseq, []]]
|
213
276
|
|
214
277
|
while (current_iseq, current_nesting = queue.shift)
|
278
|
+
file = current_iseq[5]
|
215
279
|
line = current_iseq[8]
|
216
280
|
insns = current_iseq[13]
|
217
281
|
|
@@ -246,8 +310,8 @@ module SyntaxTree
|
|
246
310
|
find_constant_path(insns, index - 1)
|
247
311
|
|
248
312
|
if superclass.empty?
|
249
|
-
|
250
|
-
|
313
|
+
warn("#{file}:#{line}: superclass with non constant path")
|
314
|
+
next
|
251
315
|
end
|
252
316
|
end
|
253
317
|
|
@@ -265,8 +329,10 @@ module SyntaxTree
|
|
265
329
|
# defined on self. We could, but it would require more
|
266
330
|
# emulation.
|
267
331
|
if insns[index - 2] != [:putself]
|
268
|
-
|
269
|
-
|
332
|
+
warn(
|
333
|
+
"#{file}:#{line}: singleton class with non-self receiver"
|
334
|
+
)
|
335
|
+
next
|
270
336
|
end
|
271
337
|
elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0
|
272
338
|
location = location_for(class_iseq)
|
@@ -290,16 +356,16 @@ module SyntaxTree
|
|
290
356
|
queue << [class_iseq, next_nesting]
|
291
357
|
when :definemethod
|
292
358
|
location = location_for(insn[2])
|
293
|
-
results <<
|
359
|
+
results << method_definition(
|
294
360
|
current_nesting,
|
295
361
|
insn[1],
|
296
362
|
location,
|
297
|
-
|
363
|
+
file_comments
|
298
364
|
)
|
299
365
|
when :definesmethod
|
300
|
-
if
|
301
|
-
|
302
|
-
|
366
|
+
if insns[index - 1] != [:putself]
|
367
|
+
warn("#{file}:#{line}: singleton method with non-self receiver")
|
368
|
+
next
|
303
369
|
end
|
304
370
|
|
305
371
|
location = location_for(insn[2])
|
@@ -309,6 +375,69 @@ module SyntaxTree
|
|
309
375
|
location,
|
310
376
|
EntryComments.new(file_comments, location)
|
311
377
|
)
|
378
|
+
when :setconstant
|
379
|
+
next_nesting = current_nesting.dup
|
380
|
+
name = insn[1]
|
381
|
+
|
382
|
+
_, nesting = find_constant_path(insns, index - 1)
|
383
|
+
next_nesting << nesting if nesting.any?
|
384
|
+
|
385
|
+
location = Location.new(line, :unknown)
|
386
|
+
results << ConstantDefinition.new(
|
387
|
+
next_nesting,
|
388
|
+
name,
|
389
|
+
location,
|
390
|
+
EntryComments.new(file_comments, location)
|
391
|
+
)
|
392
|
+
when :opt_send_without_block, :send
|
393
|
+
case insn[1][:mid]
|
394
|
+
when :attr_reader, :attr_writer, :attr_accessor
|
395
|
+
attr_names = find_attr_arguments(insns, index)
|
396
|
+
next unless attr_names
|
397
|
+
|
398
|
+
location = Location.new(line, :unknown)
|
399
|
+
attr_names.each do |attr_name|
|
400
|
+
if insn[1][:mid] != :attr_writer
|
401
|
+
results << method_definition(
|
402
|
+
current_nesting,
|
403
|
+
attr_name,
|
404
|
+
location,
|
405
|
+
file_comments
|
406
|
+
)
|
407
|
+
end
|
408
|
+
|
409
|
+
if insn[1][:mid] != :attr_reader
|
410
|
+
results << method_definition(
|
411
|
+
current_nesting,
|
412
|
+
:"#{attr_name}=",
|
413
|
+
location,
|
414
|
+
file_comments
|
415
|
+
)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
when :"core#set_method_alias"
|
419
|
+
# Now we have to validate that the alias is happening with a
|
420
|
+
# non-interpolated value. To do this we'll match the specific
|
421
|
+
# pattern we're expecting.
|
422
|
+
values =
|
423
|
+
insns[(index - 4)...index].map do |previous|
|
424
|
+
previous.is_a?(Array) ? previous[0] : previous
|
425
|
+
end
|
426
|
+
if values !=
|
427
|
+
%i[putspecialobject putspecialobject putobject putobject]
|
428
|
+
next
|
429
|
+
end
|
430
|
+
|
431
|
+
# Now that we know it's in the structure we want it, we can use
|
432
|
+
# the values of the putobject to determine the alias.
|
433
|
+
location = Location.new(line, :unknown)
|
434
|
+
results << AliasMethodDefinition.new(
|
435
|
+
current_nesting,
|
436
|
+
insns[index - 2][1],
|
437
|
+
location,
|
438
|
+
EntryComments.new(file_comments, location)
|
439
|
+
)
|
440
|
+
end
|
312
441
|
end
|
313
442
|
end
|
314
443
|
end
|
@@ -321,6 +450,20 @@ module SyntaxTree
|
|
321
450
|
# It is not as fast as using the instruction sequences directly, but is
|
322
451
|
# supported on all runtimes.
|
323
452
|
class ParserBackend
|
453
|
+
class ConstantNameVisitor < Visitor
|
454
|
+
def visit_const_ref(node)
|
455
|
+
[node.constant.value.to_sym]
|
456
|
+
end
|
457
|
+
|
458
|
+
def visit_const_path_ref(node)
|
459
|
+
visit(node.parent) << node.constant.value.to_sym
|
460
|
+
end
|
461
|
+
|
462
|
+
def visit_var_ref(node)
|
463
|
+
[node.value.value.to_sym]
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
324
467
|
class IndexVisitor < Visitor
|
325
468
|
attr_reader :results, :nesting, :statements
|
326
469
|
|
@@ -331,8 +474,46 @@ module SyntaxTree
|
|
331
474
|
end
|
332
475
|
|
333
476
|
visit_methods do
|
477
|
+
def visit_alias(node)
|
478
|
+
if node.left.is_a?(SymbolLiteral) && node.right.is_a?(SymbolLiteral)
|
479
|
+
location =
|
480
|
+
Location.new(
|
481
|
+
node.location.start_line,
|
482
|
+
node.location.start_column
|
483
|
+
)
|
484
|
+
|
485
|
+
results << AliasMethodDefinition.new(
|
486
|
+
nesting.dup,
|
487
|
+
node.left.value.value.to_sym,
|
488
|
+
location,
|
489
|
+
comments_for(node)
|
490
|
+
)
|
491
|
+
end
|
492
|
+
|
493
|
+
super
|
494
|
+
end
|
495
|
+
|
496
|
+
def visit_assign(node)
|
497
|
+
if node.target.is_a?(VarField) && node.target.value.is_a?(Const)
|
498
|
+
location =
|
499
|
+
Location.new(
|
500
|
+
node.location.start_line,
|
501
|
+
node.location.start_column
|
502
|
+
)
|
503
|
+
|
504
|
+
results << ConstantDefinition.new(
|
505
|
+
nesting.dup,
|
506
|
+
node.target.value.value.to_sym,
|
507
|
+
location,
|
508
|
+
comments_for(node)
|
509
|
+
)
|
510
|
+
end
|
511
|
+
|
512
|
+
super
|
513
|
+
end
|
514
|
+
|
334
515
|
def visit_class(node)
|
335
|
-
names =
|
516
|
+
names = node.constant.accept(ConstantNameVisitor.new)
|
336
517
|
nesting << names
|
337
518
|
|
338
519
|
location =
|
@@ -340,7 +521,7 @@ module SyntaxTree
|
|
340
521
|
|
341
522
|
superclass =
|
342
523
|
if node.superclass
|
343
|
-
visited =
|
524
|
+
visited = node.superclass.accept(ConstantNameVisitor.new)
|
344
525
|
|
345
526
|
if visited == [[]]
|
346
527
|
raise NotImplementedError, "superclass with non constant path"
|
@@ -363,12 +544,41 @@ module SyntaxTree
|
|
363
544
|
nesting.pop
|
364
545
|
end
|
365
546
|
|
366
|
-
def
|
367
|
-
|
368
|
-
|
547
|
+
def visit_command(node)
|
548
|
+
case node.message.value
|
549
|
+
when "attr_reader", "attr_writer", "attr_accessor"
|
550
|
+
comments = comments_for(node)
|
551
|
+
location =
|
552
|
+
Location.new(
|
553
|
+
node.location.start_line,
|
554
|
+
node.location.start_column
|
555
|
+
)
|
556
|
+
|
557
|
+
node.arguments.parts.each do |argument|
|
558
|
+
next unless argument.is_a?(SymbolLiteral)
|
559
|
+
name = argument.value.value.to_sym
|
560
|
+
|
561
|
+
if node.message.value != "attr_writer"
|
562
|
+
results << MethodDefinition.new(
|
563
|
+
nesting.dup,
|
564
|
+
name,
|
565
|
+
location,
|
566
|
+
comments
|
567
|
+
)
|
568
|
+
end
|
569
|
+
|
570
|
+
if node.message.value != "attr_reader"
|
571
|
+
results << MethodDefinition.new(
|
572
|
+
nesting.dup,
|
573
|
+
:"#{name}=",
|
574
|
+
location,
|
575
|
+
comments
|
576
|
+
)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
369
580
|
|
370
|
-
|
371
|
-
visit(node.parent) << node.constant.value.to_sym
|
581
|
+
super
|
372
582
|
end
|
373
583
|
|
374
584
|
def visit_def(node)
|
@@ -391,10 +601,12 @@ module SyntaxTree
|
|
391
601
|
comments_for(node)
|
392
602
|
)
|
393
603
|
end
|
604
|
+
|
605
|
+
super
|
394
606
|
end
|
395
607
|
|
396
608
|
def visit_module(node)
|
397
|
-
names =
|
609
|
+
names = node.constant.accept(ConstantNameVisitor.new)
|
398
610
|
nesting << names
|
399
611
|
|
400
612
|
location =
|
@@ -420,10 +632,6 @@ module SyntaxTree
|
|
420
632
|
@statements = node
|
421
633
|
super
|
422
634
|
end
|
423
|
-
|
424
|
-
def visit_var_ref(node)
|
425
|
-
[node.value.value.to_sym]
|
426
|
-
end
|
427
635
|
end
|
428
636
|
|
429
637
|
private
|
@@ -433,8 +641,10 @@ module SyntaxTree
|
|
433
641
|
|
434
642
|
body = statements.body
|
435
643
|
line = node.location.start_line - 1
|
436
|
-
index = body.index(node)
|
644
|
+
index = body.index(node)
|
645
|
+
return comments if index.nil?
|
437
646
|
|
647
|
+
index -= 1
|
438
648
|
while index >= 0 && body[index].is_a?(Comment) &&
|
439
649
|
(line - body[index].location.start_line < 2)
|
440
650
|
comments.unshift(body[index].value)
|
data/lib/syntax_tree/node.rb
CHANGED
@@ -792,9 +792,10 @@ module SyntaxTree
|
|
792
792
|
private
|
793
793
|
|
794
794
|
def trailing_comma?
|
795
|
+
arguments = self.arguments
|
795
796
|
return false unless arguments.is_a?(Args)
|
796
|
-
parts = arguments.parts
|
797
797
|
|
798
|
+
parts = arguments.parts
|
798
799
|
if parts.last.is_a?(ArgBlock)
|
799
800
|
# If the last argument is a block, then we can't put a trailing comma
|
800
801
|
# after it without resulting in a syntax error.
|
@@ -1188,8 +1189,11 @@ module SyntaxTree
|
|
1188
1189
|
end
|
1189
1190
|
|
1190
1191
|
def format(q)
|
1191
|
-
|
1192
|
-
|
1192
|
+
lbracket = self.lbracket
|
1193
|
+
contents = self.contents
|
1194
|
+
|
1195
|
+
if lbracket.is_a?(LBracket) && lbracket.comments.empty? && contents &&
|
1196
|
+
contents.comments.empty? && contents.parts.length > 1
|
1193
1197
|
if qwords?
|
1194
1198
|
QWordsFormatter.new(contents).format(q)
|
1195
1199
|
return
|
@@ -2091,6 +2095,7 @@ module SyntaxTree
|
|
2091
2095
|
end
|
2092
2096
|
|
2093
2097
|
def format(q)
|
2098
|
+
left = self.left
|
2094
2099
|
power = operator == :**
|
2095
2100
|
|
2096
2101
|
q.group do
|
@@ -2307,6 +2312,8 @@ module SyntaxTree
|
|
2307
2312
|
end
|
2308
2313
|
|
2309
2314
|
def bind(parser, start_char, start_column, end_char, end_column)
|
2315
|
+
rescue_clause = self.rescue_clause
|
2316
|
+
|
2310
2317
|
@location =
|
2311
2318
|
Location.new(
|
2312
2319
|
start_line: location.start_line,
|
@@ -2330,6 +2337,7 @@ module SyntaxTree
|
|
2330
2337
|
# Next we're going to determine the rescue clause if there is one
|
2331
2338
|
if rescue_clause
|
2332
2339
|
consequent = else_clause || ensure_clause
|
2340
|
+
|
2333
2341
|
rescue_clause.bind_end(
|
2334
2342
|
consequent ? consequent.location.start_char : end_char,
|
2335
2343
|
consequent ? consequent.location.start_column : end_column
|
@@ -2735,7 +2743,7 @@ module SyntaxTree
|
|
2735
2743
|
children << receiver
|
2736
2744
|
end
|
2737
2745
|
when MethodAddBlock
|
2738
|
-
if receiver.call.is_a?(CallNode) && !
|
2746
|
+
if (call = receiver.call).is_a?(CallNode) && !call.receiver.nil?
|
2739
2747
|
children << receiver
|
2740
2748
|
else
|
2741
2749
|
break
|
@@ -2744,8 +2752,8 @@ module SyntaxTree
|
|
2744
2752
|
break
|
2745
2753
|
end
|
2746
2754
|
when MethodAddBlock
|
2747
|
-
if child.call.is_a?(CallNode) && !
|
2748
|
-
children <<
|
2755
|
+
if (call = child.call).is_a?(CallNode) && !call.receiver.nil?
|
2756
|
+
children << call
|
2749
2757
|
else
|
2750
2758
|
break
|
2751
2759
|
end
|
@@ -2767,8 +2775,8 @@ module SyntaxTree
|
|
2767
2775
|
# of just Statements nodes.
|
2768
2776
|
parent = parents[3] if parent.is_a?(BlockNode) && parent.keywords?
|
2769
2777
|
|
2770
|
-
if parent.is_a?(MethodAddBlock) &&
|
2771
|
-
parent.call.message.value == "sig"
|
2778
|
+
if parent.is_a?(MethodAddBlock) &&
|
2779
|
+
(call = parent.call).is_a?(CallNode) && call.message.value == "sig"
|
2772
2780
|
threshold = 2
|
2773
2781
|
end
|
2774
2782
|
end
|
@@ -2813,10 +2821,10 @@ module SyntaxTree
|
|
2813
2821
|
|
2814
2822
|
while (child = children.pop)
|
2815
2823
|
if child.is_a?(CallNode)
|
2816
|
-
if child.receiver.is_a?(CallNode) &&
|
2817
|
-
(
|
2818
|
-
(
|
2819
|
-
(
|
2824
|
+
if (receiver = child.receiver).is_a?(CallNode) &&
|
2825
|
+
(receiver.message != :call) &&
|
2826
|
+
(receiver.message.value == "where") &&
|
2827
|
+
(message.value == "not")
|
2820
2828
|
# This is very specialized behavior wherein we group
|
2821
2829
|
# .where.not calls together because it looks better. For more
|
2822
2830
|
# information, see
|
@@ -2872,7 +2880,8 @@ module SyntaxTree
|
|
2872
2880
|
when CallNode
|
2873
2881
|
!node.receiver.nil?
|
2874
2882
|
when MethodAddBlock
|
2875
|
-
|
2883
|
+
call = node.call
|
2884
|
+
call.is_a?(CallNode) && !call.receiver.nil?
|
2876
2885
|
else
|
2877
2886
|
false
|
2878
2887
|
end
|
@@ -3629,6 +3638,10 @@ module SyntaxTree
|
|
3629
3638
|
end
|
3630
3639
|
|
3631
3640
|
def format(q)
|
3641
|
+
message = self.message
|
3642
|
+
arguments = self.arguments
|
3643
|
+
block = self.block
|
3644
|
+
|
3632
3645
|
q.group do
|
3633
3646
|
doc =
|
3634
3647
|
q.nest(0) do
|
@@ -3637,7 +3650,7 @@ module SyntaxTree
|
|
3637
3650
|
# If there are leading comments on the message then we know we have
|
3638
3651
|
# a newline in the source that is forcing these things apart. In
|
3639
3652
|
# this case we will have to use a trailing operator.
|
3640
|
-
if message.comments.any?(&:leading?)
|
3653
|
+
if message != :call && message.comments.any?(&:leading?)
|
3641
3654
|
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3642
3655
|
q.indent do
|
3643
3656
|
q.breakable_empty
|
@@ -4153,6 +4166,9 @@ module SyntaxTree
|
|
4153
4166
|
end
|
4154
4167
|
|
4155
4168
|
def format(q)
|
4169
|
+
params = self.params
|
4170
|
+
bodystmt = self.bodystmt
|
4171
|
+
|
4156
4172
|
q.group do
|
4157
4173
|
q.group do
|
4158
4174
|
q.text("def")
|
@@ -4209,6 +4225,8 @@ module SyntaxTree
|
|
4209
4225
|
end
|
4210
4226
|
|
4211
4227
|
def arity
|
4228
|
+
params = self.params
|
4229
|
+
|
4212
4230
|
case params
|
4213
4231
|
when Params
|
4214
4232
|
params.arity
|
@@ -5293,6 +5311,7 @@ module SyntaxTree
|
|
5293
5311
|
end
|
5294
5312
|
|
5295
5313
|
def child_nodes
|
5314
|
+
operator = self.operator
|
5296
5315
|
[parent, (operator if operator != :"::"), name]
|
5297
5316
|
end
|
5298
5317
|
|
@@ -5674,7 +5693,7 @@ module SyntaxTree
|
|
5674
5693
|
end
|
5675
5694
|
|
5676
5695
|
def child_nodes
|
5677
|
-
[lbrace]
|
5696
|
+
[lbrace].concat(assocs)
|
5678
5697
|
end
|
5679
5698
|
|
5680
5699
|
def copy(lbrace: nil, assocs: nil, location: nil)
|
@@ -5766,7 +5785,7 @@ module SyntaxTree
|
|
5766
5785
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
5767
5786
|
attr_reader :comments
|
5768
5787
|
|
5769
|
-
def initialize(beginning:, ending: nil, dedent: 0, parts: []
|
5788
|
+
def initialize(beginning:, location:, ending: nil, dedent: 0, parts: [])
|
5770
5789
|
@beginning = beginning
|
5771
5790
|
@ending = ending
|
5772
5791
|
@dedent = dedent
|
@@ -6134,6 +6153,8 @@ module SyntaxTree
|
|
6134
6153
|
private
|
6135
6154
|
|
6136
6155
|
def format_contents(q, parts, nested)
|
6156
|
+
keyword_rest = self.keyword_rest
|
6157
|
+
|
6137
6158
|
q.group { q.seplist(parts) { |part| q.format(part, stackable: false) } }
|
6138
6159
|
|
6139
6160
|
# If there isn't a constant, and there's a blank keyword_rest, then we
|
@@ -6763,10 +6784,13 @@ module SyntaxTree
|
|
6763
6784
|
|
6764
6785
|
def format(q)
|
6765
6786
|
keyword = "in "
|
6787
|
+
pattern = self.pattern
|
6788
|
+
consequent = self.consequent
|
6766
6789
|
|
6767
6790
|
q.group do
|
6768
6791
|
q.text(keyword)
|
6769
6792
|
q.nest(keyword.length) { q.format(pattern) }
|
6793
|
+
q.text(" then") if pattern.is_a?(RangeNode) && pattern.right.nil?
|
6770
6794
|
|
6771
6795
|
unless statements.empty?
|
6772
6796
|
q.indent do
|
@@ -7164,6 +7188,8 @@ module SyntaxTree
|
|
7164
7188
|
end
|
7165
7189
|
|
7166
7190
|
def format(q)
|
7191
|
+
params = self.params
|
7192
|
+
|
7167
7193
|
q.text("->")
|
7168
7194
|
q.group do
|
7169
7195
|
if params.is_a?(Paren)
|
@@ -7642,7 +7668,7 @@ module SyntaxTree
|
|
7642
7668
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
7643
7669
|
attr_reader :comments
|
7644
7670
|
|
7645
|
-
def initialize(parts:, comma: false
|
7671
|
+
def initialize(parts:, location:, comma: false)
|
7646
7672
|
@parts = parts
|
7647
7673
|
@comma = comma
|
7648
7674
|
@location = location
|
@@ -7703,7 +7729,7 @@ module SyntaxTree
|
|
7703
7729
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
7704
7730
|
attr_reader :comments
|
7705
7731
|
|
7706
|
-
def initialize(contents:, comma: false
|
7732
|
+
def initialize(contents:, location:, comma: false)
|
7707
7733
|
@contents = contents
|
7708
7734
|
@comma = comma
|
7709
7735
|
@location = location
|
@@ -8286,14 +8312,14 @@ module SyntaxTree
|
|
8286
8312
|
attr_reader :comments
|
8287
8313
|
|
8288
8314
|
def initialize(
|
8315
|
+
location:,
|
8289
8316
|
requireds: [],
|
8290
8317
|
optionals: [],
|
8291
8318
|
rest: nil,
|
8292
8319
|
posts: [],
|
8293
8320
|
keywords: [],
|
8294
8321
|
keyword_rest: nil,
|
8295
|
-
block: nil
|
8296
|
-
location:
|
8322
|
+
block: nil
|
8297
8323
|
)
|
8298
8324
|
@requireds = requireds
|
8299
8325
|
@optionals = optionals
|
@@ -8320,6 +8346,8 @@ module SyntaxTree
|
|
8320
8346
|
end
|
8321
8347
|
|
8322
8348
|
def child_nodes
|
8349
|
+
keyword_rest = self.keyword_rest
|
8350
|
+
|
8323
8351
|
[
|
8324
8352
|
*requireds,
|
8325
8353
|
*optionals.flatten(1),
|
@@ -8374,16 +8402,19 @@ module SyntaxTree
|
|
8374
8402
|
end
|
8375
8403
|
|
8376
8404
|
def format(q)
|
8405
|
+
rest = self.rest
|
8406
|
+
keyword_rest = self.keyword_rest
|
8407
|
+
|
8377
8408
|
parts = [
|
8378
8409
|
*requireds,
|
8379
8410
|
*optionals.map { |(name, value)| OptionalFormatter.new(name, value) }
|
8380
8411
|
]
|
8381
8412
|
|
8382
8413
|
parts << rest if rest && !rest.is_a?(ExcessedComma)
|
8383
|
-
parts
|
8384
|
-
|
8385
|
-
|
8386
|
-
|
8414
|
+
parts.concat(posts)
|
8415
|
+
parts.concat(
|
8416
|
+
keywords.map { |(name, value)| KeywordFormatter.new(name, value) }
|
8417
|
+
)
|
8387
8418
|
|
8388
8419
|
parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
|
8389
8420
|
parts << block if block
|
@@ -8510,6 +8541,8 @@ module SyntaxTree
|
|
8510
8541
|
end
|
8511
8542
|
|
8512
8543
|
def format(q)
|
8544
|
+
contents = self.contents
|
8545
|
+
|
8513
8546
|
q.group do
|
8514
8547
|
q.format(lparen)
|
8515
8548
|
|
@@ -9424,11 +9457,11 @@ module SyntaxTree
|
|
9424
9457
|
end_column: end_column
|
9425
9458
|
)
|
9426
9459
|
|
9427
|
-
if consequent
|
9428
|
-
|
9460
|
+
if (next_node = consequent)
|
9461
|
+
next_node.bind_end(end_char, end_column)
|
9429
9462
|
statements.bind_end(
|
9430
|
-
|
9431
|
-
|
9463
|
+
next_node.location.start_char,
|
9464
|
+
next_node.location.start_column
|
9432
9465
|
)
|
9433
9466
|
else
|
9434
9467
|
statements.bind_end(end_char, end_column)
|
@@ -9871,8 +9904,8 @@ module SyntaxTree
|
|
9871
9904
|
end_column: end_column
|
9872
9905
|
)
|
9873
9906
|
|
9874
|
-
if body[0].is_a?(VoidStmt)
|
9875
|
-
location =
|
9907
|
+
if (void_stmt = body[0]).is_a?(VoidStmt)
|
9908
|
+
location = void_stmt.location
|
9876
9909
|
location =
|
9877
9910
|
Location.new(
|
9878
9911
|
start_line: location.start_line,
|
@@ -10351,7 +10384,7 @@ module SyntaxTree
|
|
10351
10384
|
opening_quote, closing_quote =
|
10352
10385
|
if !Quotes.locked?(self, q.quote)
|
10353
10386
|
[q.quote, q.quote]
|
10354
|
-
elsif quote
|
10387
|
+
elsif quote&.start_with?("%")
|
10355
10388
|
[quote, Quotes.matching(quote[/%[qQ]?(.)/, 1])]
|
10356
10389
|
else
|
10357
10390
|
[quote, quote]
|
@@ -11520,7 +11553,7 @@ module SyntaxTree
|
|
11520
11553
|
end
|
11521
11554
|
|
11522
11555
|
def child_nodes
|
11523
|
-
[value]
|
11556
|
+
value == :nil ? [] : [value]
|
11524
11557
|
end
|
11525
11558
|
|
11526
11559
|
def copy(value: nil, location: nil)
|
data/lib/syntax_tree/parser.rb
CHANGED
@@ -2132,13 +2132,20 @@ module SyntaxTree
|
|
2132
2132
|
ending = consequent || consume_keyword(:end)
|
2133
2133
|
|
2134
2134
|
statements_start = pattern
|
2135
|
-
if (token =
|
2135
|
+
if (token = find_keyword_between(:then, pattern, statements))
|
2136
2136
|
tokens.delete(token)
|
2137
2137
|
statements_start = token
|
2138
2138
|
end
|
2139
2139
|
|
2140
2140
|
start_char =
|
2141
2141
|
find_next_statement_start((token || statements_start).location.end_char)
|
2142
|
+
|
2143
|
+
# Ripper ignores parentheses on patterns, so we need to do the same in
|
2144
|
+
# order to attach comments correctly to the pattern.
|
2145
|
+
if source[start_char] == ")"
|
2146
|
+
start_char = find_next_statement_start(start_char + 1)
|
2147
|
+
end
|
2148
|
+
|
2142
2149
|
statements.bind(
|
2143
2150
|
self,
|
2144
2151
|
start_char,
|
@@ -138,12 +138,13 @@ module SyntaxTree
|
|
138
138
|
# as a placeholder for collecting all of the various places that nodes are
|
139
139
|
# used.
|
140
140
|
class Node
|
141
|
-
attr_reader :name, :comment, :attributes
|
141
|
+
attr_reader :name, :comment, :attributes, :visitor_method
|
142
142
|
|
143
|
-
def initialize(name, comment, attributes)
|
143
|
+
def initialize(name, comment, attributes, visitor_method)
|
144
144
|
@name = name
|
145
145
|
@comment = comment
|
146
146
|
@attributes = attributes
|
147
|
+
@visitor_method = visitor_method
|
147
148
|
end
|
148
149
|
end
|
149
150
|
|
@@ -183,10 +184,11 @@ module SyntaxTree
|
|
183
184
|
next unless main_statement.is_a?(SyntaxTree::ClassDeclaration)
|
184
185
|
|
185
186
|
# Ensure we're looking at class declarations with superclasses.
|
186
|
-
|
187
|
+
superclass = main_statement.superclass
|
188
|
+
next unless superclass.is_a?(SyntaxTree::VarRef)
|
187
189
|
|
188
190
|
# Ensure we're looking at class declarations that inherit from Node.
|
189
|
-
next unless
|
191
|
+
next unless superclass.value.value == "Node"
|
190
192
|
|
191
193
|
# All child nodes inherit the location attr_reader from Node, so we'll add
|
192
194
|
# that to the list of attributes first.
|
@@ -195,6 +197,10 @@ module SyntaxTree
|
|
195
197
|
Attribute.new(:location, "[Location] the location of this node")
|
196
198
|
}
|
197
199
|
|
200
|
+
# This is the name of the method tha gets called on the given visitor when
|
201
|
+
# the accept method is called on this node.
|
202
|
+
visitor_method = nil
|
203
|
+
|
198
204
|
statements = main_statement.bodystmt.statements.body
|
199
205
|
statements.each_with_index do |statement, statement_index|
|
200
206
|
case statement
|
@@ -224,16 +230,25 @@ module SyntaxTree
|
|
224
230
|
end
|
225
231
|
|
226
232
|
attributes[attribute.name] = attribute
|
233
|
+
when SyntaxTree::DefNode
|
234
|
+
if statement.name.value == "accept"
|
235
|
+
call_node = statement.bodystmt.statements.body.first
|
236
|
+
visitor_method = call_node.message.value.to_sym
|
237
|
+
end
|
227
238
|
end
|
228
239
|
end
|
229
240
|
|
241
|
+
# If we never found a visitor method, then we have an error.
|
242
|
+
raise if visitor_method.nil?
|
243
|
+
|
230
244
|
# Finally, set it up in the hash of nodes so that we can use it later.
|
231
245
|
comments = parse_comments(main_statements, main_statement_index)
|
232
246
|
node =
|
233
247
|
Node.new(
|
234
248
|
main_statement.constant.constant.value.to_sym,
|
235
249
|
"#{comments.join("\n")}\n",
|
236
|
-
attributes
|
250
|
+
attributes,
|
251
|
+
visitor_method
|
237
252
|
)
|
238
253
|
|
239
254
|
@nodes[node.name] = node
|
data/lib/syntax_tree/version.rb
CHANGED
@@ -875,8 +875,7 @@ module SyntaxTree
|
|
875
875
|
when Ident
|
876
876
|
iseq.putobject("local-variable")
|
877
877
|
when IVar
|
878
|
-
iseq.
|
879
|
-
iseq.defined(Defined::TYPE_IVAR, name, "instance-variable")
|
878
|
+
iseq.definedivar(name, iseq.inline_storage, "instance-variable")
|
880
879
|
when Kw
|
881
880
|
case name
|
882
881
|
when :false
|
@@ -50,7 +50,7 @@ module SyntaxTree
|
|
50
50
|
@tail_node = nil
|
51
51
|
end
|
52
52
|
|
53
|
-
def each
|
53
|
+
def each(&_blk)
|
54
54
|
return to_enum(__method__) unless block_given?
|
55
55
|
each_node { |node| yield node.value }
|
56
56
|
end
|
@@ -673,12 +673,21 @@ module SyntaxTree
|
|
673
673
|
push(ConcatStrings.new(number))
|
674
674
|
end
|
675
675
|
|
676
|
+
def defineclass(name, class_iseq, flags)
|
677
|
+
push(DefineClass.new(name, class_iseq, flags))
|
678
|
+
end
|
679
|
+
|
676
680
|
def defined(type, name, message)
|
677
681
|
push(Defined.new(type, name, message))
|
678
682
|
end
|
679
683
|
|
680
|
-
def
|
681
|
-
|
684
|
+
def definedivar(name, cache, message)
|
685
|
+
if RUBY_VERSION < "3.3"
|
686
|
+
push(PutNil.new)
|
687
|
+
push(Defined.new(Defined::TYPE_IVAR, name, message))
|
688
|
+
else
|
689
|
+
push(DefinedIVar.new(name, cache, message))
|
690
|
+
end
|
682
691
|
end
|
683
692
|
|
684
693
|
def definemethod(name, method_iseq)
|
@@ -1058,6 +1067,8 @@ module SyntaxTree
|
|
1058
1067
|
iseq.defineclass(opnds[0], from(opnds[1], options, iseq), opnds[2])
|
1059
1068
|
when :defined
|
1060
1069
|
iseq.defined(opnds[0], opnds[1], opnds[2])
|
1070
|
+
when :definedivar
|
1071
|
+
iseq.definedivar(opnds[0], opnds[1], opnds[2])
|
1061
1072
|
when :definemethod
|
1062
1073
|
iseq.definemethod(opnds[0], from(opnds[1], options, iseq))
|
1063
1074
|
when :definesmethod
|
@@ -994,6 +994,64 @@ module SyntaxTree
|
|
994
994
|
end
|
995
995
|
end
|
996
996
|
|
997
|
+
# ### Summary
|
998
|
+
#
|
999
|
+
# `definedivar` checks if an instance variable is defined. It is a
|
1000
|
+
# specialization of the `defined` instruction. It accepts three arguments:
|
1001
|
+
# the name of the instance variable, an inline cache, and the string that
|
1002
|
+
# should be pushed onto the stack in the event that the instance variable
|
1003
|
+
# is defined.
|
1004
|
+
#
|
1005
|
+
# ### Usage
|
1006
|
+
#
|
1007
|
+
# ~~~ruby
|
1008
|
+
# defined?(@value)
|
1009
|
+
# ~~~
|
1010
|
+
#
|
1011
|
+
class DefinedIVar < Instruction
|
1012
|
+
attr_reader :name, :cache, :message
|
1013
|
+
|
1014
|
+
def initialize(name, cache, message)
|
1015
|
+
@name = name
|
1016
|
+
@cache = cache
|
1017
|
+
@message = message
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
def disasm(fmt)
|
1021
|
+
fmt.instruction(
|
1022
|
+
"definedivar",
|
1023
|
+
[fmt.object(name), fmt.inline_storage(cache), fmt.object(message)]
|
1024
|
+
)
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
def to_a(_iseq)
|
1028
|
+
[:definedivar, name, cache, message]
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
def deconstruct_keys(_keys)
|
1032
|
+
{ name: name, cache: cache, message: message }
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def ==(other)
|
1036
|
+
other.is_a?(DefinedIVar) && other.name == name &&
|
1037
|
+
other.cache == cache && other.message == message
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
def length
|
1041
|
+
4
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
def pushes
|
1045
|
+
1
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def call(vm)
|
1049
|
+
result = (message if vm.frame._self.instance_variable_defined?(name))
|
1050
|
+
|
1051
|
+
vm.push(result)
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
997
1055
|
# ### Summary
|
998
1056
|
#
|
999
1057
|
# `definemethod` defines a method on the class of the current value of
|
data/lib/syntax_tree.rb
CHANGED
data/tasks/sorbet.rake
CHANGED
@@ -20,6 +20,26 @@ module SyntaxTree
|
|
20
20
|
generate_parent
|
21
21
|
Reflection.nodes.sort.each { |(_, node)| generate_node(node) }
|
22
22
|
|
23
|
+
body << ClassDeclaration(
|
24
|
+
ConstPathRef(VarRef(Const("SyntaxTree")), Const("BasicVisitor")),
|
25
|
+
nil,
|
26
|
+
BodyStmt(
|
27
|
+
Statements(generate_visitor("overridable")),
|
28
|
+
nil,
|
29
|
+
nil,
|
30
|
+
nil,
|
31
|
+
nil
|
32
|
+
),
|
33
|
+
location
|
34
|
+
)
|
35
|
+
|
36
|
+
body << ClassDeclaration(
|
37
|
+
ConstPathRef(VarRef(Const("SyntaxTree")), Const("Visitor")),
|
38
|
+
ConstPathRef(VarRef(Const("SyntaxTree")), Const("BasicVisitor")),
|
39
|
+
BodyStmt(Statements(generate_visitor("override")), nil, nil, nil, nil),
|
40
|
+
location
|
41
|
+
)
|
42
|
+
|
23
43
|
Formatter.format(nil, Program(Statements(body)))
|
24
44
|
end
|
25
45
|
|
@@ -122,8 +142,41 @@ module SyntaxTree
|
|
122
142
|
@line += 1
|
123
143
|
|
124
144
|
node_body << generate_def_node("child_nodes", nil)
|
145
|
+
@line += 2
|
146
|
+
|
147
|
+
node_body << sig_block do
|
148
|
+
CallNode(
|
149
|
+
sig_params do
|
150
|
+
BareAssocHash(
|
151
|
+
[
|
152
|
+
Assoc(
|
153
|
+
Label("other:"),
|
154
|
+
CallNode(
|
155
|
+
VarRef(Const("T")),
|
156
|
+
Period("."),
|
157
|
+
Ident("untyped"),
|
158
|
+
nil
|
159
|
+
)
|
160
|
+
)
|
161
|
+
]
|
162
|
+
)
|
163
|
+
end,
|
164
|
+
Period("."),
|
165
|
+
sig_returns { ConstPathRef(VarRef(Const("T")), Const("Boolean")) },
|
166
|
+
nil
|
167
|
+
)
|
168
|
+
end
|
125
169
|
@line += 1
|
126
170
|
|
171
|
+
node_body << generate_def_node(
|
172
|
+
"==",
|
173
|
+
Paren(
|
174
|
+
LParen("("),
|
175
|
+
Params.new(location: location, requireds: [Ident("other")])
|
176
|
+
)
|
177
|
+
)
|
178
|
+
@line += 2
|
179
|
+
|
127
180
|
node_body
|
128
181
|
end
|
129
182
|
|
@@ -195,6 +248,49 @@ module SyntaxTree
|
|
195
248
|
)
|
196
249
|
end
|
197
250
|
|
251
|
+
def generate_visitor(override)
|
252
|
+
body = []
|
253
|
+
|
254
|
+
Reflection.nodes.each do |name, node|
|
255
|
+
body << sig_block do
|
256
|
+
CallNode(
|
257
|
+
CallNode(
|
258
|
+
Ident(override),
|
259
|
+
Period("."),
|
260
|
+
sig_params do
|
261
|
+
BareAssocHash(
|
262
|
+
[
|
263
|
+
Assoc(
|
264
|
+
Label("node:"),
|
265
|
+
sig_type_for(SyntaxTree.const_get(name))
|
266
|
+
)
|
267
|
+
]
|
268
|
+
)
|
269
|
+
end,
|
270
|
+
nil
|
271
|
+
),
|
272
|
+
Period("."),
|
273
|
+
sig_returns do
|
274
|
+
CallNode(VarRef(Const("T")), Period("."), Ident("untyped"), nil)
|
275
|
+
end,
|
276
|
+
nil
|
277
|
+
)
|
278
|
+
end
|
279
|
+
|
280
|
+
body << generate_def_node(
|
281
|
+
node.visitor_method,
|
282
|
+
Paren(
|
283
|
+
LParen("("),
|
284
|
+
Params.new(requireds: [Ident("node")], location: location)
|
285
|
+
)
|
286
|
+
)
|
287
|
+
|
288
|
+
@line += 2
|
289
|
+
end
|
290
|
+
|
291
|
+
body
|
292
|
+
end
|
293
|
+
|
198
294
|
def sig_block
|
199
295
|
MethodAddBlock(
|
200
296
|
CallNode(nil, nil, Ident("sig"), nil),
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syntax_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0
|
4
|
+
version: 6.1.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-03-
|
11
|
+
date: 2023-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prettier_print
|
@@ -110,6 +110,7 @@ files:
|
|
110
110
|
- ".github/workflows/main.yml"
|
111
111
|
- ".gitignore"
|
112
112
|
- ".rubocop.yml"
|
113
|
+
- ".ruby-version"
|
113
114
|
- CHANGELOG.md
|
114
115
|
- CODE_OF_CONDUCT.md
|
115
116
|
- Gemfile
|