syntax_tree 6.0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1e39c546fb96aea864e8190b8a8ff338543afd644d2334edad5b98663d418f2
4
- data.tar.gz: 7633b68e7b42fcd782fc1c89395d93679e72828fe1a9214e741ad00718543c6b
3
+ metadata.gz: 7515a827509d352259e6cae476930093fe382fcc35c248fba3ab716019e69ca9
4
+ data.tar.gz: add2625387abe5616f90d28843f4fcebac5b68222f9a6258cbfb04fbd41b4772
5
5
  SHA512:
6
- metadata.gz: a0990a769cbc448e549d14542610ccfbf09536ada139cc10701871a506a1b160a8ce751712a94a8f003d3b2c1828a6b5d4bd5384efe63f0f82a21c0762b689d5
7
- data.tar.gz: a08b4057370c230bfd713ff9a717ea4f2a02be4f8610af51b18143ff9b2b9e542541b77c049488c500033751b50adc279d5e3a8beb0568f5e2bb165793c612d4
6
+ metadata.gz: ea0a666bd65180facaab89acd7b7bd87d3f5208c69a0fe80b91d1d25d57cdc2c281ac301070da8d73c7d119574a7f7de30c0facb0d7131f895bbc8fa7fe582c5
7
+ data.tar.gz: 4c9df489dcb876280a219a8e213a800f0544a0aaa02a303d8fc3ba948e07e195dbfb21da8f8f04825abc4bf61fb9ec92141d5a00cc7b3d53b1d6103d58d10be3
data/.gitignore CHANGED
@@ -4,6 +4,7 @@
4
4
  /coverage/
5
5
  /pkg/
6
6
  /rdocs/
7
+ /sorbet/
7
8
  /spec/reports/
8
9
  /tmp/
9
10
  /vendor/
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,31 @@ 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
+
22
+ ## [6.0.2] - 2023-03-03
23
+
24
+ ### Added
25
+
26
+ - The `WithScope` visitor mixin will now additionally report local variables defined through regular expression named captures.
27
+ - The `WithScope` visitor mixin now properly handles destructured splat arguments in required positions.
28
+
29
+ ### Changed
30
+
31
+ - Fixed the AST output by adding blocks to `Command` and `CommandCall` nodes in the `FieldVisitor`.
32
+ - Fixed the location of lambda local variables (e.g., `->(; a) {}`).
33
+
9
34
  ## [6.0.1] - 2023-02-26
10
35
 
11
36
  ### Added
@@ -572,7 +597,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
572
597
 
573
598
  - 🎉 Initial release! 🎉
574
599
 
575
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v6.0.1...HEAD
600
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v6.0.2...HEAD
601
+ [6.0.2]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v6.0.1...v6.0.2
576
602
  [6.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v6.0.0...v6.0.1
577
603
  [6.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.3.0...v6.0.0
578
604
  [5.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v5.2.0...v5.3.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (6.0.1)
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.17.0)
13
+ minitest (5.18.0)
14
14
  parallel (1.22.1)
15
- parser (3.2.1.0)
15
+ parser (3.2.1.1)
16
16
  ast (~> 2.4.1)
17
- prettier_print (1.2.0)
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.46.0)
22
+ rubocop (1.48.1)
23
23
  json (~> 2.3)
24
24
  parallel (~> 1.10)
25
25
  parser (>= 3.2.0.0)
@@ -29,9 +29,9 @@ GEM
29
29
  rubocop-ast (>= 1.26.0, < 2.0)
30
30
  ruby-progressbar (~> 1.7)
31
31
  unicode-display_width (>= 2.4.0, < 3.0)
32
- rubocop-ast (1.26.0)
32
+ rubocop-ast (1.27.0)
33
33
  parser (>= 3.2.1.0)
34
- ruby-progressbar (1.11.0)
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
 
@@ -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"
@@ -263,6 +263,7 @@ module SyntaxTree
263
263
  node(node, "command") do
264
264
  field("message", node.message)
265
265
  field("arguments", node.arguments)
266
+ field("block", node.block) if node.block
266
267
  comments(node)
267
268
  end
268
269
  end
@@ -273,6 +274,7 @@ module SyntaxTree
273
274
  field("operator", node.operator)
274
275
  field("message", node.message)
275
276
  field("arguments", node.arguments) if node.arguments
277
+ field("block", node.block) if node.block
276
278
  comments(node)
277
279
  end
278
280
  end
@@ -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 insns[index].is_a?(Integer)
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
- raise NotImplementedError,
250
- "superclass with non constant path on line #{line}"
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
- raise NotImplementedError,
269
- "singleton class with non-self receiver"
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 << MethodDefinition.new(
359
+ results << method_definition(
294
360
  current_nesting,
295
361
  insn[1],
296
362
  location,
297
- EntryComments.new(file_comments, location)
363
+ file_comments
298
364
  )
299
365
  when :definesmethod
300
- if current_iseq[13][index - 1] != [:putself]
301
- raise NotImplementedError,
302
- "singleton method with non-self receiver"
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 = visit(node.constant)
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 = visit(node.superclass)
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 visit_const_ref(node)
367
- [node.constant.value.to_sym]
368
- end
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
- def visit_const_path_ref(node)
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 = visit(node.constant)
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) - 1
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)