syntax_tree 6.0.2 → 6.1.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 +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
|