prism 1.9.0 → 2.0.0.rc2

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.
Files changed (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -0
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/LICENSE.md +1 -1
  5. data/Makefile +41 -12
  6. data/README.md +35 -97
  7. data/config.yml +174 -161
  8. data/docs/build_system.md +2 -2
  9. data/docs/configuration.md +5 -4
  10. data/docs/encoding.md +2 -2
  11. data/docs/fuzzing.md +4 -11
  12. data/docs/javascript.md +19 -4
  13. data/docs/mapping.md +1 -1
  14. data/docs/releasing.md +77 -5
  15. data/docs/relocation.md +2 -4
  16. data/docs/serialization.md +22 -24
  17. data/docs/testing.md +46 -29
  18. data/ext/prism/api_node.c +1461 -1401
  19. data/ext/prism/extconf.rb +3 -4
  20. data/ext/prism/extension.c +796 -422
  21. data/ext/prism/extension.h +3 -3
  22. data/include/prism/arena.h +37 -0
  23. data/include/prism/ast.h +2727 -610
  24. data/include/prism/buffer.h +52 -0
  25. data/include/prism/comments.h +55 -0
  26. data/include/prism/compiler/accel.h +26 -0
  27. data/include/prism/compiler/align.h +36 -0
  28. data/include/prism/compiler/assume.h +25 -0
  29. data/include/prism/compiler/bswap.h +46 -0
  30. data/include/prism/compiler/endianness.h +34 -0
  31. data/include/prism/compiler/exported.h +24 -0
  32. data/include/prism/compiler/fallthrough.h +22 -0
  33. data/include/prism/compiler/filesystem.h +32 -0
  34. data/include/prism/compiler/flex_array.h +19 -0
  35. data/include/prism/compiler/force_inline.h +21 -0
  36. data/include/prism/compiler/format.h +25 -0
  37. data/include/prism/compiler/inline.h +17 -0
  38. data/include/prism/compiler/nodiscard.h +22 -0
  39. data/include/prism/compiler/nonnull.h +18 -0
  40. data/include/prism/compiler/unused.h +18 -0
  41. data/include/prism/constant_pool.h +81 -0
  42. data/include/prism/diagnostic.h +38 -403
  43. data/include/prism/errors_format.h +53 -0
  44. data/include/prism/excludes.h +29 -0
  45. data/include/prism/integer.h +41 -0
  46. data/include/prism/internal/allocator.h +68 -0
  47. data/include/prism/internal/allocator_debug.h +88 -0
  48. data/include/prism/internal/arena.h +108 -0
  49. data/include/prism/internal/bit.h +42 -0
  50. data/include/prism/internal/buffer.h +94 -0
  51. data/include/prism/internal/char.h +145 -0
  52. data/include/prism/internal/comments.h +53 -0
  53. data/include/prism/internal/constant_pool.h +152 -0
  54. data/include/prism/internal/diagnostic.h +389 -0
  55. data/include/prism/{encoding.h → internal/encoding.h} +27 -68
  56. data/include/prism/internal/integer.h +68 -0
  57. data/include/prism/internal/isinf.h +16 -0
  58. data/include/prism/internal/line_offset_list.h +34 -0
  59. data/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  60. data/include/prism/internal/magic_comments.h +23 -0
  61. data/include/prism/internal/memchr.h +15 -0
  62. data/include/prism/internal/node.h +38 -0
  63. data/include/prism/internal/options.h +232 -0
  64. data/include/prism/internal/parser.h +1004 -0
  65. data/include/prism/internal/regexp.h +26 -0
  66. data/include/prism/internal/serialize.h +39 -0
  67. data/include/prism/internal/source.h +72 -0
  68. data/include/prism/{static_literals.h → internal/static_literals.h} +26 -47
  69. data/include/prism/internal/stringy.h +35 -0
  70. data/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  71. data/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  72. data/include/prism/internal/tokens.h +11 -0
  73. data/include/prism/internal/xxh3.h +16 -0
  74. data/include/prism/json.h +32 -0
  75. data/include/prism/line_offset_list.h +61 -0
  76. data/include/prism/magic_comments.h +35 -0
  77. data/include/prism/node.h +18 -53
  78. data/include/prism/options.h +146 -280
  79. data/include/prism/parser.h +356 -867
  80. data/include/prism/prettyprint.h +6 -9
  81. data/include/prism/serialize.h +115 -0
  82. data/include/prism/source.h +155 -0
  83. data/include/prism/stream.h +28 -0
  84. data/include/prism/string_query.h +74 -0
  85. data/include/prism/stringy.h +72 -0
  86. data/include/prism/version.h +12 -3
  87. data/include/prism.h +43 -320
  88. data/lib/prism/compiler.rb +319 -305
  89. data/lib/prism/desugar_compiler.rb +105 -34
  90. data/lib/prism/dispatcher.rb +646 -473
  91. data/lib/prism/dot_visitor.rb +592 -511
  92. data/lib/prism/dsl.rb +391 -25
  93. data/lib/prism/ffi.rb +258 -90
  94. data/lib/prism/inspect_visitor.rb +357 -325
  95. data/lib/prism/lex_compat.rb +254 -212
  96. data/lib/prism/mutation_compiler.rb +313 -305
  97. data/lib/prism/node.rb +20966 -6992
  98. data/lib/prism/node_ext.rb +180 -187
  99. data/lib/prism/node_find.rb +215 -0
  100. data/lib/prism/parse_result/errors.rb +7 -1
  101. data/lib/prism/parse_result/newlines.rb +276 -8
  102. data/lib/prism/parse_result.rb +468 -139
  103. data/lib/prism/pattern.rb +65 -20
  104. data/lib/prism/reflection.rb +23 -12
  105. data/lib/prism/relocation.rb +149 -87
  106. data/lib/prism/serialize.rb +3460 -539
  107. data/lib/prism/string_query.rb +24 -1
  108. data/lib/prism/translation/parser/builder.rb +10 -2
  109. data/lib/prism/translation/parser/compiler.rb +44 -52
  110. data/lib/prism/translation/parser/lexer.rb +41 -92
  111. data/lib/prism/translation/parser.rb +1 -1
  112. data/lib/prism/translation/parser_current.rb +1 -1
  113. data/lib/prism/translation/ripper/lexer.rb +8 -10
  114. data/lib/prism/translation/ripper/sexp.rb +2 -10
  115. data/lib/prism/translation/ripper/shim.rb +2 -0
  116. data/lib/prism/translation/ripper.rb +967 -209
  117. data/lib/prism/translation/ruby_parser.rb +73 -316
  118. data/lib/prism/translation.rb +2 -0
  119. data/lib/prism/visitor.rb +632 -5
  120. data/lib/prism.rb +79 -9
  121. data/prism.gemspec +128 -62
  122. data/rbi/generated/prism/compiler.rbi +489 -0
  123. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  124. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  125. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  126. data/rbi/generated/prism/dsl.rbi +751 -0
  127. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  128. data/rbi/generated/prism/lex_compat.rbi +162 -0
  129. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  130. data/rbi/generated/prism/node.rbi +14407 -0
  131. data/rbi/generated/prism/node_ext.rbi +259 -0
  132. data/rbi/generated/prism/node_find.rbi +79 -0
  133. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  134. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  135. data/rbi/generated/prism/parse_result.rbi +776 -0
  136. data/rbi/generated/prism/pattern.rbi +134 -0
  137. data/rbi/generated/prism/reflection.rbi +89 -0
  138. data/rbi/generated/prism/relocation.rbi +329 -0
  139. data/rbi/generated/prism/serialize.rbi +184 -0
  140. data/rbi/generated/prism/string_query.rbi +43 -0
  141. data/rbi/generated/prism/translation.rbi +7 -0
  142. data/rbi/generated/prism/visitor.rbi +650 -0
  143. data/rbi/generated/prism.rbi +86 -0
  144. data/rbi/prism/translation/parser_versions.rbi +5 -0
  145. data/rbi/rubyvm/node_find.rbi +14 -0
  146. data/sig/generated/prism/compiler.rbs +492 -0
  147. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  148. data/sig/generated/prism/dispatcher.rbs +1292 -0
  149. data/sig/generated/prism/dot_visitor.rbs +635 -0
  150. data/sig/generated/prism/dsl.rbs +928 -0
  151. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  152. data/sig/generated/prism/lex_compat.rbs +166 -0
  153. data/sig/{prism → generated/prism}/mutation_compiler.rbs +309 -4
  154. data/sig/generated/prism/node.rbs +20896 -0
  155. data/sig/generated/prism/node_ext.rbs +290 -0
  156. data/sig/generated/prism/node_find.rbs +90 -0
  157. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  158. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  159. data/sig/generated/prism/parse_result.rbs +871 -0
  160. data/sig/generated/prism/pattern.rbs +156 -0
  161. data/sig/generated/prism/reflection.rbs +101 -0
  162. data/sig/generated/prism/relocation.rbs +428 -0
  163. data/sig/generated/prism/serialize.rbs +198 -0
  164. data/sig/generated/prism/string_query.rbs +43 -0
  165. data/sig/generated/prism/translation.rbs +8 -0
  166. data/sig/generated/prism/visitor.rbs +1111 -0
  167. data/sig/generated/prism.rbs +84 -0
  168. data/sig/manifest.yaml +2 -0
  169. data/src/arena.c +117 -0
  170. data/src/{util/pm_buffer.c → buffer.c} +53 -21
  171. data/src/{util/pm_char.c → char.c} +28 -57
  172. data/src/comments.c +454 -0
  173. data/src/constant_pool.c +406 -0
  174. data/src/diagnostic.c +97 -65
  175. data/src/encoding.c +8 -3
  176. data/src/errors_format.c +519 -0
  177. data/src/{util/pm_integer.c → integer.c} +33 -22
  178. data/src/json.c +5733 -0
  179. data/src/line_offset_list.c +100 -0
  180. data/src/list.c +24 -0
  181. data/src/{util/pm_memchr.c → memchr.c} +8 -6
  182. data/src/node.c +3504 -7674
  183. data/src/options.c +162 -49
  184. data/src/parser.c +367 -0
  185. data/src/prettyprint.c +421 -354
  186. data/src/prism.c +8402 -7161
  187. data/src/regexp.c +961 -45
  188. data/src/serialize.c +460 -424
  189. data/src/source.c +518 -0
  190. data/src/static_literals.c +111 -43
  191. data/src/string_query.c +180 -0
  192. data/src/stringy.c +91 -0
  193. data/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  194. data/src/strpbrk.c +447 -0
  195. data/src/{token_type.c → tokens.c} +39 -18
  196. data/src/xxh3.c +292 -0
  197. metadata +128 -63
  198. data/ext/prism/api_pack.c +0 -276
  199. data/include/prism/defines.h +0 -260
  200. data/include/prism/pack.h +0 -163
  201. data/include/prism/regexp.h +0 -43
  202. data/include/prism/util/pm_buffer.h +0 -236
  203. data/include/prism/util/pm_char.h +0 -204
  204. data/include/prism/util/pm_constant_pool.h +0 -218
  205. data/include/prism/util/pm_integer.h +0 -130
  206. data/include/prism/util/pm_memchr.h +0 -29
  207. data/include/prism/util/pm_newline_list.h +0 -113
  208. data/include/prism/util/pm_string.h +0 -200
  209. data/lib/prism/pack.rb +0 -230
  210. data/lib/prism/parse_result/comments.rb +0 -188
  211. data/rbi/prism/compiler.rbi +0 -12
  212. data/rbi/prism/dsl.rbi +0 -524
  213. data/rbi/prism/inspect_visitor.rbi +0 -12
  214. data/rbi/prism/node.rbi +0 -8750
  215. data/rbi/prism/node_ext.rbi +0 -107
  216. data/rbi/prism/parse_result.rbi +0 -404
  217. data/rbi/prism/reflection.rbi +0 -58
  218. data/rbi/prism/string_query.rbi +0 -12
  219. data/rbi/prism/visitor.rbi +0 -473
  220. data/rbi/prism.rbi +0 -63
  221. data/sig/prism/compiler.rbs +0 -9
  222. data/sig/prism/dispatcher.rbs +0 -19
  223. data/sig/prism/dot_visitor.rbs +0 -6
  224. data/sig/prism/dsl.rbs +0 -351
  225. data/sig/prism/inspect_visitor.rbs +0 -22
  226. data/sig/prism/lex_compat.rbs +0 -10
  227. data/sig/prism/node.rbs +0 -4042
  228. data/sig/prism/node_ext.rbs +0 -149
  229. data/sig/prism/pack.rbs +0 -43
  230. data/sig/prism/parse_result/comments.rbs +0 -38
  231. data/sig/prism/parse_result.rbs +0 -197
  232. data/sig/prism/pattern.rbs +0 -13
  233. data/sig/prism/reflection.rbs +0 -50
  234. data/sig/prism/relocation.rbs +0 -185
  235. data/sig/prism/serialize.rbs +0 -8
  236. data/sig/prism/string_query.rbs +0 -11
  237. data/sig/prism/visitor.rbs +0 -169
  238. data/sig/prism.rbs +0 -272
  239. data/src/pack.c +0 -509
  240. data/src/util/pm_constant_pool.c +0 -342
  241. data/src/util/pm_list.c +0 -49
  242. data/src/util/pm_newline_list.c +0 -125
  243. data/src/util/pm_string.c +0 -381
  244. data/src/util/pm_strpbrk.c +0 -206
@@ -22,22 +22,10 @@ module Prism
22
22
  # - on_comma
23
23
  # - on_ignored_nl
24
24
  # - on_ignored_sp
25
- # - on_kw
26
- # - on_label_end
27
- # - on_lbrace
28
- # - on_lbracket
29
- # - on_lparen
30
25
  # - on_nl
31
- # - on_op
32
26
  # - on_operator_ambiguous
33
- # - on_rbrace
34
- # - on_rbracket
35
- # - on_rparen
36
27
  # - on_semicolon
37
28
  # - on_sp
38
- # - on_symbeg
39
- # - on_tstring_beg
40
- # - on_tstring_end
41
29
  #
42
30
  class Ripper < Compiler
43
31
  # Parses the given Ruby program read from +src+.
@@ -69,7 +57,8 @@ module Prism
69
57
  # [[1, 13], :on_kw, "end", END ]]
70
58
  #
71
59
  def self.lex(src, filename = "-", lineno = 1, raise_errors: false)
72
- result = Prism.lex_compat(src, filepath: filename, line: lineno, version: "current")
60
+ coerced = coerce_source(src)
61
+ result = Prism.lex_compat(coerced, filepath: filename, line: lineno, version: "current", encoding: coerced.encoding)
73
62
 
74
63
  if result.failure? && raise_errors
75
64
  raise SyntaxError, result.errors.first.message
@@ -88,7 +77,22 @@ module Prism
88
77
  # # => ["def", " ", "m", "(", "a", ")", " ", "nil", " ", "end"]
89
78
  #
90
79
  def self.tokenize(...)
91
- lex(...).map(&:value)
80
+ lex(...).map { |token| token[2] }
81
+ end
82
+
83
+ # Mirros the various lex_types that ripper supports
84
+ def self.coerce_source(source) # :nodoc:
85
+ if source.is_a?(IO)
86
+ source.read
87
+ elsif source.respond_to?(:gets)
88
+ src = +""
89
+ while line = source.gets
90
+ src << line
91
+ end
92
+ src
93
+ else
94
+ source.to_str
95
+ end
92
96
  end
93
97
 
94
98
  # This contains a table of all of the parser events and their
@@ -343,7 +347,7 @@ module Prism
343
347
  "__ENCODING__",
344
348
  "__FILE__",
345
349
  "__LINE__"
346
- ]
350
+ ].to_set
347
351
 
348
352
  # A list of all of the Ruby binary operators.
349
353
  BINARY_OPERATORS = [
@@ -368,7 +372,7 @@ module Prism
368
372
  :/,
369
373
  :*,
370
374
  :**
371
- ]
375
+ ].to_set
372
376
 
373
377
  private_constant :KEYWORDS, :BINARY_OPERATORS
374
378
 
@@ -442,6 +446,64 @@ module Prism
442
446
  autoload :SexpBuilder, "prism/translation/ripper/sexp"
443
447
  autoload :SexpBuilderPP, "prism/translation/ripper/sexp"
444
448
 
449
+ # Provides optimized access to line and column information.
450
+ # Ripper bounds are mostly accessed in a linear fashion, so
451
+ # we can try a linear scan first and fall back to binary search.
452
+ class LineAndColumnCache # :nodoc:
453
+ # How many should it look ahead/behind before falling back to binary searching.
454
+ WINDOW = 8
455
+ private_constant :WINDOW
456
+
457
+ #: (Source source) -> void
458
+ def initialize(source)
459
+ @source = source
460
+ @offsets = source.offsets
461
+ @hint = 0
462
+ end
463
+
464
+ #: (Integer byte_offset) -> [Integer, Integer]
465
+ def line_and_column(byte_offset)
466
+ @hint = new_hint(byte_offset) || @source.find_line(byte_offset)
467
+ return [@hint + @source.start_line, byte_offset - @offsets[@hint]]
468
+ end
469
+
470
+ private
471
+
472
+ def new_hint(byte_offset)
473
+ if @offsets[@hint] <= byte_offset
474
+ # Same line?
475
+ if (@hint + 1 >= @offsets.size || @offsets[@hint + 1] > byte_offset)
476
+ return @hint
477
+ end
478
+
479
+ # Scan forwards
480
+ limit = [@hint + WINDOW + 1, @offsets.size].min
481
+ idx = @hint + 1
482
+ while idx < limit
483
+ if @offsets[idx] > byte_offset
484
+ return idx - 1
485
+ end
486
+ if @offsets[idx] == byte_offset
487
+ return idx
488
+ end
489
+ idx += 1
490
+ end
491
+ else
492
+ # Scan backwards
493
+ limit = @hint > WINDOW ? @hint - WINDOW : 0
494
+ idx = @hint
495
+ while idx >= limit + 1
496
+ if @offsets[idx - 1] <= byte_offset
497
+ return idx - 1
498
+ end
499
+ idx -= 1
500
+ end
501
+ end
502
+
503
+ nil
504
+ end
505
+ end
506
+
445
507
  # :stopdoc:
446
508
  # This is not part of the public API but used by some gems.
447
509
 
@@ -475,16 +537,17 @@ module Prism
475
537
  # The current line number of the parser.
476
538
  attr_reader :lineno
477
539
 
478
- # The current column number of the parser.
540
+ # The current column in bytes of the parser.
479
541
  attr_reader :column
480
542
 
481
543
  # Create a new Translation::Ripper object with the given source.
482
544
  def initialize(source, filename = "(ripper)", lineno = 1)
483
- @source = source
545
+ @source = Ripper.coerce_source(source)
484
546
  @filename = filename
485
547
  @lineno = lineno
486
548
  @column = 0
487
549
  @result = nil
550
+ @line_and_column_cache = nil
488
551
  end
489
552
 
490
553
  ##########################################################################
@@ -503,7 +566,12 @@ module Prism
503
566
  bounds(location)
504
567
 
505
568
  if comment.is_a?(InlineComment)
506
- on_comment(comment.slice)
569
+ # Inline comments always contain a newline if the line itself contains it
570
+ if result.source.source.bytesize > comment.location.end_offset
571
+ on_comment("#{comment.slice}\n")
572
+ else
573
+ on_comment(comment.slice)
574
+ end
507
575
  else
508
576
  offset = location.start_offset
509
577
  lines = comment.slice.lines
@@ -580,13 +648,28 @@ module Prism
580
648
  end
581
649
  end
582
650
 
651
+ # Return encoding of the source.
652
+ def encoding
653
+ result.source.encoding
654
+ end
655
+
656
+ # Return true if parsed source ended by `__END__`.
657
+ def end_seen?
658
+ !!result.data_loc
659
+ end
660
+
583
661
  ##########################################################################
584
662
  # Visitor methods
585
663
  ##########################################################################
586
664
 
665
+ # :stopdoc:
666
+
587
667
  # alias foo bar
588
668
  # ^^^^^^^^^^^^^
589
669
  def visit_alias_method_node(node)
670
+ bounds(node.keyword_loc)
671
+ on_kw("alias")
672
+
590
673
  new_name = visit(node.new_name)
591
674
  old_name = visit(node.old_name)
592
675
 
@@ -597,6 +680,9 @@ module Prism
597
680
  # alias $foo $bar
598
681
  # ^^^^^^^^^^^^^^^
599
682
  def visit_alias_global_variable_node(node)
683
+ bounds(node.keyword_loc)
684
+ on_kw("alias")
685
+
600
686
  new_name = visit_alias_global_variable_node_value(node.new_name)
601
687
  old_name = visit_alias_global_variable_node_value(node.old_name)
602
688
 
@@ -622,6 +708,10 @@ module Prism
622
708
  # ^^^^^^^^^
623
709
  def visit_alternation_pattern_node(node)
624
710
  left = visit_pattern_node(node.left)
711
+
712
+ bounds(node.operator_loc)
713
+ on_op("|")
714
+
625
715
  right = visit_pattern_node(node.right)
626
716
 
627
717
  bounds(node.location)
@@ -632,7 +722,13 @@ module Prism
632
722
  # parenthesis node that can be used to wrap patterns.
633
723
  private def visit_pattern_node(node)
634
724
  if node.is_a?(ParenthesesNode)
635
- visit(node.body)
725
+ bounds(node.opening_loc)
726
+ on_lparen("(")
727
+ result = visit(node.body)
728
+ bounds(node.closing_loc)
729
+ on_rparen(")")
730
+
731
+ result
636
732
  else
637
733
  visit(node)
638
734
  end
@@ -642,6 +738,14 @@ module Prism
642
738
  # ^^^^^^^
643
739
  def visit_and_node(node)
644
740
  left = visit(node.left)
741
+
742
+ bounds(node.operator_loc)
743
+ if node.operator == "and"
744
+ on_kw("and")
745
+ else
746
+ on_op("&&")
747
+ end
748
+
645
749
  right = visit(node.right)
646
750
 
647
751
  bounds(node.location)
@@ -669,6 +773,8 @@ module Prism
669
773
  previous = element
670
774
  end
671
775
 
776
+ visit_words_sep(opening_loc, node.elements.last, node.closing_loc)
777
+
672
778
  bounds(node.closing_loc)
673
779
  on_tstring_end(node.closing)
674
780
  when /^%i/
@@ -683,11 +789,13 @@ module Prism
683
789
  visit_words_sep(opening_loc, previous, element)
684
790
 
685
791
  bounds(element.location)
686
- elements = on_qsymbols_add(elements, on_tstring_content(element.value))
792
+ elements = on_qsymbols_add(elements, on_tstring_content(element.content))
687
793
 
688
794
  previous = element
689
795
  end
690
796
 
797
+ visit_words_sep(opening_loc, node.elements.last, node.closing_loc)
798
+
691
799
  bounds(node.closing_loc)
692
800
  on_tstring_end(node.closing)
693
801
  when /^%W/
@@ -725,6 +833,8 @@ module Prism
725
833
  previous = element
726
834
  end
727
835
 
836
+ visit_words_sep(opening_loc, node.elements.last, node.closing_loc)
837
+
728
838
  bounds(node.closing_loc)
729
839
  on_tstring_end(node.closing)
730
840
  when /^%I/
@@ -743,7 +853,7 @@ module Prism
743
853
  on_symbols_add(
744
854
  elements,
745
855
  if element.is_a?(SymbolNode)
746
- on_word_add(on_word_new, on_tstring_content(element.value))
856
+ on_word_add(on_word_new, on_tstring_content(element.content))
747
857
  else
748
858
  element.parts.inject(on_word_new) do |word, part|
749
859
  word_part =
@@ -762,6 +872,8 @@ module Prism
762
872
  previous = element
763
873
  end
764
874
 
875
+ visit_words_sep(opening_loc, node.elements.last, node.closing_loc)
876
+
765
877
  bounds(node.closing_loc)
766
878
  on_tstring_end(node.closing)
767
879
  else
@@ -778,15 +890,21 @@ module Prism
778
890
  on_array(elements)
779
891
  end
780
892
 
781
- # Dispatch a words_sep event that contains the space between the elements
893
+ # Dispatch words_sep events that contains the whitespace between the elements
782
894
  # of list literals.
783
895
  private def visit_words_sep(opening_loc, previous, current)
784
- end_offset = (previous.nil? ? opening_loc : previous.location).end_offset
785
- start_offset = current.location.start_offset
786
-
787
- if end_offset != start_offset
788
- bounds(current.location.copy(start_offset: end_offset))
789
- on_words_sep(source.byteslice(end_offset...start_offset))
896
+ start_offset = (previous.nil? ? opening_loc : previous.location).end_offset
897
+ end_offset = current.start_offset
898
+ length = end_offset - start_offset
899
+
900
+ if length > 0
901
+ whitespace = source.byteslice(start_offset, length)
902
+ current_offset = start_offset
903
+ whitespace.each_line do |part|
904
+ bounds(opening_loc.copy(start_offset: current_offset, length: part.bytesize))
905
+ on_words_sep(part)
906
+ current_offset += part.bytesize
907
+ end
790
908
  end
791
909
  end
792
910
 
@@ -812,9 +930,18 @@ module Prism
812
930
  # ^^^^^
813
931
  def visit_array_pattern_node(node)
814
932
  constant = visit(node.constant)
933
+
934
+ if node.opening_loc
935
+ bounds(node.opening_loc)
936
+ node.opening == "[" ? on_lbracket("[") : on_lparen("(")
937
+ end
938
+
815
939
  requireds = visit_all(node.requireds) if node.requireds.any?
816
940
  rest =
817
941
  if (rest_node = node.rest).is_a?(SplatNode)
942
+ bounds(rest_node.operator_loc)
943
+ on_op("*")
944
+
818
945
  if rest_node.expression.nil?
819
946
  bounds(rest_node.location)
820
947
  on_var_field(nil)
@@ -825,6 +952,10 @@ module Prism
825
952
 
826
953
  posts = visit_all(node.posts) if node.posts.any?
827
954
 
955
+ if node.closing_loc
956
+ bounds(node.closing_loc)
957
+ node.closing == "]" ? on_rbracket("]") : on_rparen(")")
958
+ end
828
959
  bounds(node.location)
829
960
  on_aryptn(constant, requireds, rest, posts)
830
961
  end
@@ -832,7 +963,7 @@ module Prism
832
963
  # foo(bar)
833
964
  # ^^^
834
965
  def visit_arguments_node(node)
835
- arguments, _, _ = visit_call_node_arguments(node, nil, false)
966
+ arguments, _ = visit_call_node_arguments(node, nil, false)
836
967
  arguments
837
968
  end
838
969
 
@@ -840,6 +971,12 @@ module Prism
840
971
  # ^^^^
841
972
  def visit_assoc_node(node)
842
973
  key = visit(node.key)
974
+
975
+ if node.operator_loc
976
+ bounds(node.operator_loc)
977
+ on_op("=>")
978
+ end
979
+
843
980
  value = visit(node.value)
844
981
 
845
982
  bounds(node.location)
@@ -852,6 +989,9 @@ module Prism
852
989
  # { **foo }
853
990
  # ^^^^^
854
991
  def visit_assoc_splat_node(node)
992
+ bounds(node.operator_loc)
993
+ on_op("**")
994
+
855
995
  value = visit(node.value)
856
996
 
857
997
  bounds(node.location)
@@ -868,8 +1008,18 @@ module Prism
868
1008
  # begin end
869
1009
  # ^^^^^^^^^
870
1010
  def visit_begin_node(node)
1011
+ if node.begin_keyword_loc
1012
+ bounds(node.begin_keyword_loc)
1013
+ on_kw("begin")
1014
+ end
1015
+
871
1016
  clauses = visit_begin_node_clauses(node.begin_keyword_loc, node, false)
872
1017
 
1018
+ if node.end_keyword_loc
1019
+ bounds(node.end_keyword_loc)
1020
+ on_kw("end")
1021
+ end
1022
+
873
1023
  bounds(node.location)
874
1024
  on_begin(clauses)
875
1025
  end
@@ -881,7 +1031,7 @@ module Prism
881
1031
  on_stmts_add(on_stmts_new, on_void_stmt)
882
1032
  else
883
1033
  body = node.statements.body
884
- body.unshift(nil) if void_stmt?(location, node.statements.body[0].location, allow_newline)
1034
+ body = [nil, *body] if void_stmt?(location, node.statements.body[0].location, allow_newline)
885
1035
 
886
1036
  bounds(node.statements.location)
887
1037
  visit_statements_node_body(body)
@@ -890,12 +1040,15 @@ module Prism
890
1040
  rescue_clause = visit(node.rescue_clause)
891
1041
  else_clause =
892
1042
  unless (else_clause_node = node.else_clause).nil?
1043
+ bounds(else_clause_node.else_keyword_loc)
1044
+ on_kw("else")
1045
+
893
1046
  else_statements =
894
1047
  if else_clause_node.statements.nil?
895
1048
  [nil]
896
1049
  else
897
1050
  body = else_clause_node.statements.body
898
- body.unshift(nil) if void_stmt?(else_clause_node.else_keyword_loc, else_clause_node.statements.body[0].location, allow_newline)
1051
+ body = [nil, *body] if void_stmt?(else_clause_node.else_keyword_loc, else_clause_node.statements.body[0].location, allow_newline)
899
1052
  body
900
1053
  end
901
1054
 
@@ -917,7 +1070,7 @@ module Prism
917
1070
  on_bodystmt(visit_statements_node_body([nil]), nil, nil, nil)
918
1071
  when StatementsNode
919
1072
  body = [*node.body]
920
- body.unshift(nil) if void_stmt?(location, body[0].location, allow_newline)
1073
+ body = [nil, *body] if void_stmt?(location, body[0].location, allow_newline)
921
1074
  stmts = visit_statements_node_body(body)
922
1075
 
923
1076
  bounds(node.body.first.location)
@@ -932,6 +1085,8 @@ module Prism
932
1085
  # foo(&bar)
933
1086
  # ^^^^
934
1087
  def visit_block_argument_node(node)
1088
+ bounds(node.operator_loc)
1089
+ on_op("&")
935
1090
  visit(node.expression)
936
1091
  end
937
1092
 
@@ -945,6 +1100,13 @@ module Prism
945
1100
  # Visit a BlockNode.
946
1101
  def visit_block_node(node)
947
1102
  braces = node.opening == "{"
1103
+ bounds(node.opening_loc)
1104
+ if braces
1105
+ on_lbrace("{")
1106
+ else
1107
+ on_kw("do")
1108
+ end
1109
+
948
1110
  parameters = visit(node.parameters)
949
1111
 
950
1112
  body =
@@ -957,7 +1119,7 @@ module Prism
957
1119
  braces ? stmts : on_bodystmt(stmts, nil, nil, nil)
958
1120
  when StatementsNode
959
1121
  stmts = node.body.body
960
- stmts.unshift(nil) if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false)
1122
+ stmts = [nil, *stmts] if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false)
961
1123
  stmts = visit_statements_node_body(stmts)
962
1124
 
963
1125
  bounds(node.body.location)
@@ -968,6 +1130,14 @@ module Prism
968
1130
  raise
969
1131
  end
970
1132
 
1133
+ if braces
1134
+ bounds(node.closing_loc)
1135
+ on_rbrace("}")
1136
+ else
1137
+ bounds(node.closing_loc)
1138
+ on_kw("end")
1139
+ end
1140
+
971
1141
  if braces
972
1142
  bounds(node.location)
973
1143
  on_brace_block(parameters, body)
@@ -980,12 +1150,15 @@ module Prism
980
1150
  # def foo(&bar); end
981
1151
  # ^^^^
982
1152
  def visit_block_parameter_node(node)
1153
+ bounds(node.operator_loc)
1154
+ on_op("&")
1155
+
983
1156
  if node.name_loc.nil?
984
1157
  bounds(node.location)
985
1158
  on_blockarg(nil)
986
1159
  else
987
1160
  bounds(node.name_loc)
988
- name = visit_token(node.name.to_s)
1161
+ name = on_ident(node.name.to_s)
989
1162
 
990
1163
  bounds(node.location)
991
1164
  on_blockarg(name)
@@ -994,6 +1167,9 @@ module Prism
994
1167
 
995
1168
  # A block's parameters.
996
1169
  def visit_block_parameters_node(node)
1170
+ bounds(node.opening_loc)
1171
+ on_op("|")
1172
+
997
1173
  parameters =
998
1174
  if node.parameters.nil?
999
1175
  on_params(nil, nil, nil, nil, nil, nil, nil)
@@ -1008,6 +1184,9 @@ module Prism
1008
1184
  false
1009
1185
  end
1010
1186
 
1187
+ bounds(node.closing_loc)
1188
+ on_op("|")
1189
+
1011
1190
  bounds(node.location)
1012
1191
  on_block_var(parameters, locals)
1013
1192
  end
@@ -1018,6 +1197,9 @@ module Prism
1018
1197
  # break foo
1019
1198
  # ^^^^^^^^^
1020
1199
  def visit_break_node(node)
1200
+ bounds(node.keyword_loc)
1201
+ on_kw("break")
1202
+
1021
1203
  if node.arguments.nil?
1022
1204
  bounds(node.location)
1023
1205
  on_break(on_args_new)
@@ -1042,12 +1224,21 @@ module Prism
1042
1224
  case node.name
1043
1225
  when :[]
1044
1226
  receiver = visit(node.receiver)
1045
- arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
1227
+
1228
+ bounds(node.opening_loc)
1229
+ on_lbracket("[")
1230
+
1231
+ arguments, block_node = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
1232
+
1233
+ bounds(node.closing_loc)
1234
+ on_rbracket("]")
1235
+
1236
+ block = visit(block_node)
1046
1237
 
1047
1238
  bounds(node.location)
1048
1239
  call = on_aref(receiver, arguments)
1049
1240
 
1050
- if has_ripper_block
1241
+ if block_node
1051
1242
  bounds(node.location)
1052
1243
  on_method_add_block(call, block)
1053
1244
  else
@@ -1056,6 +1247,9 @@ module Prism
1056
1247
  when :[]=
1057
1248
  receiver = visit(node.receiver)
1058
1249
 
1250
+ bounds(node.opening_loc)
1251
+ on_lbracket("[")
1252
+
1059
1253
  *arguments, last_argument = node.arguments.arguments
1060
1254
  arguments << node.block if !node.block.nil?
1061
1255
 
@@ -1071,6 +1265,11 @@ module Prism
1071
1265
  end
1072
1266
  end
1073
1267
 
1268
+ bounds(node.closing_loc)
1269
+ on_rbracket("]")
1270
+ bounds(node.equal_loc)
1271
+ on_op("=")
1272
+
1074
1273
  bounds(node.location)
1075
1274
  call = on_aref_field(receiver, arguments)
1076
1275
  value = visit_write_value(last_argument)
@@ -1078,27 +1277,54 @@ module Prism
1078
1277
  bounds(last_argument.location)
1079
1278
  on_assign(call, value)
1080
1279
  when :-@, :+@, :~
1081
- receiver = visit(node.receiver)
1280
+ bounds(node.message_loc)
1281
+ on_op(node.message)
1082
1282
 
1283
+ receiver = visit(node.receiver)
1083
1284
  bounds(node.location)
1084
1285
  on_unary(node.name, receiver)
1085
1286
  when :!
1287
+ bounds(node.message_loc)
1086
1288
  if node.message == "not"
1289
+ on_kw("not")
1290
+
1291
+ if node.opening_loc
1292
+ bounds(node.opening_loc)
1293
+ on_lparen("(")
1294
+ end
1295
+
1087
1296
  receiver =
1088
- if !node.receiver.is_a?(ParenthesesNode) || !node.receiver.body.nil?
1297
+ if node.receiver.is_a?(ParenthesesNode) && node.receiver.body.nil?
1298
+ # The parens in `not()` just emit parens and nothing else.
1299
+ bounds(node.receiver.opening_loc)
1300
+ on_lparen("(")
1301
+ bounds(node.receiver.closing_loc)
1302
+ on_rparen(")")
1303
+ nil
1304
+ else
1089
1305
  visit(node.receiver)
1090
1306
  end
1091
1307
 
1308
+ if node.closing_loc
1309
+ bounds(node.closing_loc)
1310
+ on_rparen(")")
1311
+ end
1092
1312
  bounds(node.location)
1093
1313
  on_unary(:not, receiver)
1094
1314
  else
1315
+ on_op("!")
1316
+
1095
1317
  receiver = visit(node.receiver)
1096
1318
 
1097
1319
  bounds(node.location)
1098
1320
  on_unary(:!, receiver)
1099
1321
  end
1100
- when *BINARY_OPERATORS
1322
+ when BINARY_OPERATORS
1101
1323
  receiver = visit(node.receiver)
1324
+
1325
+ bounds(node.message_loc)
1326
+ on_op(node.message)
1327
+
1102
1328
  value = visit(node.arguments.arguments.first)
1103
1329
 
1104
1330
  bounds(node.location)
@@ -1110,7 +1336,19 @@ module Prism
1110
1336
  if node.variable_call?
1111
1337
  on_vcall(message)
1112
1338
  else
1113
- arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
1339
+ if node.opening_loc
1340
+ bounds(node.opening_loc)
1341
+ on_lparen("(")
1342
+ end
1343
+
1344
+ arguments, block_node = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
1345
+
1346
+ if node.closing_loc
1347
+ bounds(node.closing_loc)
1348
+ on_rparen(")")
1349
+ end
1350
+
1351
+ block = visit(block_node)
1114
1352
  call =
1115
1353
  if node.opening_loc.nil? && get_arguments_and_block(node.arguments, node.block).first.any?
1116
1354
  bounds(node.location)
@@ -1123,7 +1361,7 @@ module Prism
1123
1361
  on_method_add_arg(on_fcall(message), on_args_new)
1124
1362
  end
1125
1363
 
1126
- if has_ripper_block
1364
+ if block_node
1127
1365
  bounds(node.block.location)
1128
1366
  on_method_add_block(call, block)
1129
1367
  else
@@ -1135,7 +1373,7 @@ module Prism
1135
1373
  receiver = visit(node.receiver)
1136
1374
 
1137
1375
  bounds(node.call_operator_loc)
1138
- call_operator = visit_token(node.call_operator)
1376
+ call_operator = visit_call_operator(node.call_operator)
1139
1377
 
1140
1378
  message =
1141
1379
  if node.message_loc.nil?
@@ -1145,13 +1383,30 @@ module Prism
1145
1383
  visit_token(node.message, false)
1146
1384
  end
1147
1385
 
1386
+ if node.equal_loc
1387
+ bounds(node.equal_loc)
1388
+ on_op("=")
1389
+ end
1390
+
1148
1391
  if node.name.end_with?("=") && !node.message.end_with?("=") && !node.arguments.nil? && node.block.nil?
1149
1392
  value = visit_write_value(node.arguments.arguments.first)
1150
1393
 
1151
1394
  bounds(node.location)
1152
1395
  on_assign(on_field(receiver, call_operator, message), value)
1153
1396
  else
1154
- arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
1397
+ if node.opening_loc
1398
+ bounds(node.opening_loc)
1399
+ on_lparen("(")
1400
+ end
1401
+
1402
+ arguments, block_node = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
1403
+
1404
+ if node.closing_loc
1405
+ bounds(node.closing_loc)
1406
+ on_rparen(")")
1407
+ end
1408
+
1409
+ block = visit(block_node)
1155
1410
  call =
1156
1411
  if node.opening_loc.nil?
1157
1412
  bounds(node.location)
@@ -1169,7 +1424,7 @@ module Prism
1169
1424
  on_method_add_arg(on_call(receiver, call_operator, message), arguments)
1170
1425
  end
1171
1426
 
1172
- if has_ripper_block
1427
+ if block_node
1173
1428
  bounds(node.block.location)
1174
1429
  on_method_add_block(call, block)
1175
1430
  else
@@ -1211,8 +1466,7 @@ module Prism
1211
1466
  on_args_add_block(args, false)
1212
1467
  end
1213
1468
  end,
1214
- visit(block),
1215
- block != nil,
1469
+ block,
1216
1470
  ]
1217
1471
  end
1218
1472
 
@@ -1230,7 +1484,7 @@ module Prism
1230
1484
  receiver = visit(node.receiver)
1231
1485
 
1232
1486
  bounds(node.call_operator_loc)
1233
- call_operator = visit_token(node.call_operator)
1487
+ call_operator = visit_call_operator(node.call_operator)
1234
1488
 
1235
1489
  bounds(node.message_loc)
1236
1490
  message = visit_token(node.message)
@@ -1252,7 +1506,7 @@ module Prism
1252
1506
  receiver = visit(node.receiver)
1253
1507
 
1254
1508
  bounds(node.call_operator_loc)
1255
- call_operator = visit_token(node.call_operator)
1509
+ call_operator = visit_call_operator(node.call_operator)
1256
1510
 
1257
1511
  bounds(node.message_loc)
1258
1512
  message = visit_token(node.message)
@@ -1274,7 +1528,7 @@ module Prism
1274
1528
  receiver = visit(node.receiver)
1275
1529
 
1276
1530
  bounds(node.call_operator_loc)
1277
- call_operator = visit_token(node.call_operator)
1531
+ call_operator = visit_call_operator(node.call_operator)
1278
1532
 
1279
1533
  bounds(node.message_loc)
1280
1534
  message = visit_token(node.message)
@@ -1296,6 +1550,9 @@ module Prism
1296
1550
  if node.call_operator == "::"
1297
1551
  receiver = visit(node.receiver)
1298
1552
 
1553
+ bounds(node.call_operator_loc)
1554
+ on_op("::")
1555
+
1299
1556
  bounds(node.message_loc)
1300
1557
  message = visit_token(node.message)
1301
1558
 
@@ -1305,7 +1562,7 @@ module Prism
1305
1562
  receiver = visit(node.receiver)
1306
1563
 
1307
1564
  bounds(node.call_operator_loc)
1308
- call_operator = visit_token(node.call_operator)
1565
+ call_operator = visit_call_operator(node.call_operator)
1309
1566
 
1310
1567
  bounds(node.message_loc)
1311
1568
  message = visit_token(node.message)
@@ -1319,6 +1576,10 @@ module Prism
1319
1576
  # ^^^^^^^^^^
1320
1577
  def visit_capture_pattern_node(node)
1321
1578
  value = visit(node.value)
1579
+
1580
+ bounds(node.operator_loc)
1581
+ on_op("=>")
1582
+
1322
1583
  target = visit(node.target)
1323
1584
 
1324
1585
  bounds(node.location)
@@ -1328,10 +1589,21 @@ module Prism
1328
1589
  # case foo; when bar; end
1329
1590
  # ^^^^^^^^^^^^^^^^^^^^^^^
1330
1591
  def visit_case_node(node)
1592
+ bounds(node.case_keyword_loc)
1593
+ on_kw("case")
1594
+
1331
1595
  predicate = visit(node.predicate)
1596
+ visited_conditions = node.conditions.map { |condition| visit(condition) }
1597
+ visited_else_clause = visit(node.else_clause)
1598
+
1599
+ if !node.else_clause
1600
+ bounds(node.end_keyword_loc)
1601
+ on_kw("end")
1602
+ end
1603
+
1332
1604
  clauses =
1333
- node.conditions.reverse_each.inject(visit(node.else_clause)) do |current, condition|
1334
- on_when(*visit(condition), current)
1605
+ visited_conditions.reverse_each.inject(visited_else_clause) do |current, condition|
1606
+ on_when(*condition, current)
1335
1607
  end
1336
1608
 
1337
1609
  bounds(node.location)
@@ -1341,10 +1613,23 @@ module Prism
1341
1613
  # case foo; in bar; end
1342
1614
  # ^^^^^^^^^^^^^^^^^^^^^
1343
1615
  def visit_case_match_node(node)
1616
+ bounds(node.case_keyword_loc)
1617
+ on_kw("case")
1618
+
1344
1619
  predicate = visit(node.predicate)
1620
+ visited_conditions = node.conditions.map do | condition|
1621
+ visit(condition)
1622
+ end
1623
+ visited_else_clause = visit(node.else_clause)
1624
+
1625
+ if !node.else_clause
1626
+ bounds(node.end_keyword_loc)
1627
+ on_kw("end")
1628
+ end
1629
+
1345
1630
  clauses =
1346
- node.conditions.reverse_each.inject(visit(node.else_clause)) do |current, condition|
1347
- on_in(*visit(condition), current)
1631
+ visited_conditions.reverse_each.inject(visited_else_clause) do |current, condition|
1632
+ on_in(*condition, current)
1348
1633
  end
1349
1634
 
1350
1635
  bounds(node.location)
@@ -1354,6 +1639,9 @@ module Prism
1354
1639
  # class Foo; end
1355
1640
  # ^^^^^^^^^^^^^^
1356
1641
  def visit_class_node(node)
1642
+ bounds(node.class_keyword_loc)
1643
+ on_kw("class")
1644
+
1357
1645
  constant_path =
1358
1646
  if node.constant_path.is_a?(ConstantReadNode)
1359
1647
  bounds(node.constant_path.location)
@@ -1362,9 +1650,17 @@ module Prism
1362
1650
  visit(node.constant_path)
1363
1651
  end
1364
1652
 
1653
+ if node.inheritance_operator_loc
1654
+ bounds(node.inheritance_operator_loc)
1655
+ on_op("<")
1656
+ end
1657
+
1365
1658
  superclass = visit(node.superclass)
1366
1659
  bodystmt = visit_body_node(node.superclass&.location || node.constant_path.location, node.body, node.superclass.nil?)
1367
1660
 
1661
+ bounds(node.end_keyword_loc)
1662
+ on_kw("end")
1663
+
1368
1664
  bounds(node.location)
1369
1665
  on_class(constant_path, superclass, bodystmt)
1370
1666
  end
@@ -1378,12 +1674,13 @@ module Prism
1378
1674
 
1379
1675
  # @@foo = 1
1380
1676
  # ^^^^^^^^^
1381
- #
1382
- # @@foo, @@bar = 1
1383
- # ^^^^^ ^^^^^
1384
1677
  def visit_class_variable_write_node(node)
1385
1678
  bounds(node.name_loc)
1386
1679
  target = on_var_field(on_cvar(node.name.to_s))
1680
+
1681
+ bounds(node.operator_loc)
1682
+ on_op("=")
1683
+
1387
1684
  value = visit_write_value(node.value)
1388
1685
 
1389
1686
  bounds(node.location)
@@ -1448,12 +1745,13 @@ module Prism
1448
1745
 
1449
1746
  # Foo = 1
1450
1747
  # ^^^^^^^
1451
- #
1452
- # Foo, Bar = 1
1453
- # ^^^ ^^^
1454
1748
  def visit_constant_write_node(node)
1455
1749
  bounds(node.name_loc)
1456
1750
  target = on_var_field(on_const(node.name.to_s))
1751
+
1752
+ bounds(node.operator_loc)
1753
+ on_op("=")
1754
+
1457
1755
  value = visit_write_value(node.value)
1458
1756
 
1459
1757
  bounds(node.location)
@@ -1513,6 +1811,11 @@ module Prism
1513
1811
  # ^^^^^^^^
1514
1812
  def visit_constant_path_node(node)
1515
1813
  if node.parent.nil?
1814
+ if node.delimiter_loc
1815
+ bounds(node.delimiter_loc)
1816
+ on_op("::")
1817
+ end
1818
+
1516
1819
  bounds(node.name_loc)
1517
1820
  child = on_const(node.name.to_s)
1518
1821
 
@@ -1521,6 +1824,9 @@ module Prism
1521
1824
  else
1522
1825
  parent = visit(node.parent)
1523
1826
 
1827
+ bounds(node.delimiter_loc)
1828
+ on_op("::")
1829
+
1524
1830
  bounds(node.name_loc)
1525
1831
  child = on_const(node.name.to_s)
1526
1832
 
@@ -1531,11 +1837,12 @@ module Prism
1531
1837
 
1532
1838
  # Foo::Bar = 1
1533
1839
  # ^^^^^^^^^^^^
1534
- #
1535
- # Foo::Foo, Bar::Bar = 1
1536
- # ^^^^^^^^ ^^^^^^^^
1537
1840
  def visit_constant_path_write_node(node)
1538
1841
  target = visit_constant_path_write_node_target(node.target)
1842
+
1843
+ bounds(node.operator_loc)
1844
+ on_op("=")
1845
+
1539
1846
  value = visit_write_value(node.value)
1540
1847
 
1541
1848
  bounds(node.location)
@@ -1545,6 +1852,11 @@ module Prism
1545
1852
  # Visit a constant path that is part of a write node.
1546
1853
  private def visit_constant_path_write_node_target(node)
1547
1854
  if node.parent.nil?
1855
+ if node.delimiter_loc
1856
+ bounds(node.delimiter_loc)
1857
+ on_op("::")
1858
+ end
1859
+
1548
1860
  bounds(node.name_loc)
1549
1861
  child = on_const(node.name.to_s)
1550
1862
 
@@ -1553,6 +1865,9 @@ module Prism
1553
1865
  else
1554
1866
  parent = visit(node.parent)
1555
1867
 
1868
+ bounds(node.delimiter_loc)
1869
+ on_op("::")
1870
+
1556
1871
  bounds(node.name_loc)
1557
1872
  child = on_const(node.name.to_s)
1558
1873
 
@@ -1565,7 +1880,6 @@ module Prism
1565
1880
  # ^^^^^^^^^^^^^^^
1566
1881
  def visit_constant_path_operator_write_node(node)
1567
1882
  target = visit_constant_path_write_node_target(node.target)
1568
- value = visit(node.value)
1569
1883
 
1570
1884
  bounds(node.binary_operator_loc)
1571
1885
  operator = on_op("#{node.binary_operator}=")
@@ -1579,7 +1893,6 @@ module Prism
1579
1893
  # ^^^^^^^^^^^^^^^^
1580
1894
  def visit_constant_path_and_write_node(node)
1581
1895
  target = visit_constant_path_write_node_target(node.target)
1582
- value = visit(node.value)
1583
1896
 
1584
1897
  bounds(node.operator_loc)
1585
1898
  operator = on_op("&&=")
@@ -1593,7 +1906,6 @@ module Prism
1593
1906
  # ^^^^^^^^^^^^^^^^
1594
1907
  def visit_constant_path_or_write_node(node)
1595
1908
  target = visit_constant_path_write_node_target(node.target)
1596
- value = visit(node.value)
1597
1909
 
1598
1910
  bounds(node.operator_loc)
1599
1911
  operator = on_op("||=")
@@ -1615,16 +1927,24 @@ module Prism
1615
1927
  # def self.foo; end
1616
1928
  # ^^^^^^^^^^^^^^^^^
1617
1929
  def visit_def_node(node)
1930
+ bounds(node.def_keyword_loc)
1931
+ on_kw("def")
1932
+
1618
1933
  receiver = visit(node.receiver)
1619
1934
  operator =
1620
1935
  if !node.operator_loc.nil?
1621
1936
  bounds(node.operator_loc)
1622
- visit_token(node.operator)
1937
+ node.operator == "." ? on_period(".") : on_op("::")
1623
1938
  end
1624
1939
 
1625
1940
  bounds(node.name_loc)
1626
1941
  name = visit_token(node.name_loc.slice)
1627
1942
 
1943
+ if node.lparen_loc
1944
+ bounds(node.lparen_loc)
1945
+ on_lparen("(")
1946
+ end
1947
+
1628
1948
  parameters =
1629
1949
  if node.parameters.nil?
1630
1950
  bounds(node.location)
@@ -1634,10 +1954,17 @@ module Prism
1634
1954
  end
1635
1955
 
1636
1956
  if !node.lparen_loc.nil?
1957
+ bounds(node.rparen_loc)
1958
+ on_rparen(")")
1637
1959
  bounds(node.lparen_loc)
1638
1960
  parameters = on_paren(parameters)
1639
1961
  end
1640
1962
 
1963
+ if node.equal_loc
1964
+ bounds(node.equal_loc)
1965
+ on_op("=")
1966
+ end
1967
+
1641
1968
  bodystmt =
1642
1969
  if node.equal_loc.nil?
1643
1970
  visit_body_node(node.rparen_loc || node.end_keyword_loc, node.body)
@@ -1648,6 +1975,11 @@ module Prism
1648
1975
  on_bodystmt(body, nil, nil, nil)
1649
1976
  end
1650
1977
 
1978
+ if node.end_keyword_loc
1979
+ bounds(node.end_keyword_loc)
1980
+ on_kw("end")
1981
+ end
1982
+
1651
1983
  bounds(node.location)
1652
1984
  if receiver
1653
1985
  on_defs(receiver, operator, name, parameters, bodystmt)
@@ -1662,8 +1994,21 @@ module Prism
1662
1994
  # defined?(a)
1663
1995
  # ^^^^^^^^^^^
1664
1996
  def visit_defined_node(node)
1997
+ bounds(node.keyword_loc)
1998
+ on_kw("defined?")
1999
+
2000
+ if node.lparen_loc
2001
+ bounds(node.lparen_loc)
2002
+ on_lparen("(")
2003
+ end
2004
+
1665
2005
  expression = visit(node.value)
1666
2006
 
2007
+ if node.rparen_loc
2008
+ bounds(node.rparen_loc)
2009
+ on_rparen(")")
2010
+ end
2011
+
1667
2012
  # Very weird circumstances here where something like:
1668
2013
  #
1669
2014
  # defined?
@@ -1684,17 +2029,24 @@ module Prism
1684
2029
  # if foo then bar else baz end
1685
2030
  # ^^^^^^^^^^^^
1686
2031
  def visit_else_node(node)
2032
+ bounds(node.else_keyword_loc)
2033
+ on_kw("else")
2034
+
1687
2035
  statements =
1688
2036
  if node.statements.nil?
1689
2037
  [nil]
1690
2038
  else
1691
2039
  body = node.statements.body
1692
- body.unshift(nil) if void_stmt?(node.else_keyword_loc, node.statements.body[0].location, false)
2040
+ body = [nil, *body] if void_stmt?(node.else_keyword_loc, node.statements.body[0].location, false)
1693
2041
  body
1694
2042
  end
1695
2043
 
2044
+ else_statements = visit_statements_node_body(statements)
2045
+
2046
+ bounds(node.end_keyword_loc)
2047
+ on_kw("end")
1696
2048
  bounds(node.location)
1697
- on_else(visit_statements_node_body(statements))
2049
+ on_else(else_statements)
1698
2050
  end
1699
2051
 
1700
2052
  # "foo #{bar}"
@@ -1732,12 +2084,15 @@ module Prism
1732
2084
 
1733
2085
  # Visit an EnsureNode node.
1734
2086
  def visit_ensure_node(node)
2087
+ bounds(node.ensure_keyword_loc)
2088
+ on_kw("ensure")
2089
+
1735
2090
  statements =
1736
2091
  if node.statements.nil?
1737
2092
  [nil]
1738
2093
  else
1739
2094
  body = node.statements.body
1740
- body.unshift(nil) if void_stmt?(node.ensure_keyword_loc, body[0].location, false)
2095
+ body = [nil, *body] if void_stmt?(node.ensure_keyword_loc, body[0].location, false)
1741
2096
  body
1742
2097
  end
1743
2098
 
@@ -1758,6 +2113,14 @@ module Prism
1758
2113
  # ^^^^^^^^^^^
1759
2114
  def visit_find_pattern_node(node)
1760
2115
  constant = visit(node.constant)
2116
+
2117
+ if node.opening_loc
2118
+ bounds(node.opening_loc)
2119
+ node.opening == "[" ? on_lbracket("[") : on_lparen("(")
2120
+ end
2121
+ bounds(node.left.operator_loc)
2122
+ on_op("*")
2123
+
1761
2124
  left =
1762
2125
  if node.left.expression.nil?
1763
2126
  bounds(node.left.location)
@@ -1767,6 +2130,10 @@ module Prism
1767
2130
  end
1768
2131
 
1769
2132
  requireds = visit_all(node.requireds) if node.requireds.any?
2133
+
2134
+ bounds(node.right.operator_loc)
2135
+ on_op("*")
2136
+
1770
2137
  right =
1771
2138
  if node.right.expression.nil?
1772
2139
  bounds(node.right.location)
@@ -1775,6 +2142,10 @@ module Prism
1775
2142
  visit(node.right.expression)
1776
2143
  end
1777
2144
 
2145
+ if node.closing_loc
2146
+ bounds(node.closing_loc)
2147
+ node.closing == "]" ? on_rbracket("]") : on_rparen(")")
2148
+ end
1778
2149
  bounds(node.location)
1779
2150
  on_fndptn(constant, left, requireds, right)
1780
2151
  end
@@ -1783,6 +2154,10 @@ module Prism
1783
2154
  # ^^^^^^^^^^
1784
2155
  def visit_flip_flop_node(node)
1785
2156
  left = visit(node.left)
2157
+
2158
+ bounds(node.operator_loc)
2159
+ on_op(node.operator)
2160
+
1786
2161
  right = visit(node.right)
1787
2162
 
1788
2163
  bounds(node.location)
@@ -1802,8 +2177,18 @@ module Prism
1802
2177
  # for foo in bar do end
1803
2178
  # ^^^^^^^^^^^^^^^^^^^^^
1804
2179
  def visit_for_node(node)
2180
+ bounds(node.for_keyword_loc)
2181
+ on_kw("for")
2182
+
1805
2183
  index = visit(node.index)
2184
+ bounds(node.in_keyword_loc)
2185
+ on_kw("in")
2186
+
1806
2187
  collection = visit(node.collection)
2188
+ if node.do_keyword_loc
2189
+ bounds(node.do_keyword_loc)
2190
+ on_kw("do")
2191
+ end
1807
2192
  statements =
1808
2193
  if node.statements.nil?
1809
2194
  bounds(node.location)
@@ -1812,6 +2197,9 @@ module Prism
1812
2197
  visit(node.statements)
1813
2198
  end
1814
2199
 
2200
+ bounds(node.end_keyword_loc)
2201
+ on_kw("end")
2202
+
1815
2203
  bounds(node.location)
1816
2204
  on_for(index, collection, statements)
1817
2205
  end
@@ -1820,6 +2208,7 @@ module Prism
1820
2208
  # ^^^
1821
2209
  def visit_forwarding_arguments_node(node)
1822
2210
  bounds(node.location)
2211
+ on_op("...")
1823
2212
  on_args_forward
1824
2213
  end
1825
2214
 
@@ -1827,6 +2216,7 @@ module Prism
1827
2216
  # ^^^
1828
2217
  def visit_forwarding_parameter_node(node)
1829
2218
  bounds(node.location)
2219
+ on_op("...")
1830
2220
  on_args_forward
1831
2221
  end
1832
2222
 
@@ -1836,6 +2226,9 @@ module Prism
1836
2226
  # super {}
1837
2227
  # ^^^^^^^^
1838
2228
  def visit_forwarding_super_node(node)
2229
+ bounds(node.keyword_loc)
2230
+ on_kw("super")
2231
+
1839
2232
  if node.block.nil?
1840
2233
  bounds(node.location)
1841
2234
  on_zsuper
@@ -1856,12 +2249,13 @@ module Prism
1856
2249
 
1857
2250
  # $foo = 1
1858
2251
  # ^^^^^^^^
1859
- #
1860
- # $foo, $bar = 1
1861
- # ^^^^ ^^^^
1862
2252
  def visit_global_variable_write_node(node)
1863
2253
  bounds(node.name_loc)
1864
2254
  target = on_var_field(on_gvar(node.name.to_s))
2255
+
2256
+ bounds(node.operator_loc)
2257
+ on_op("=")
2258
+
1865
2259
  value = visit_write_value(node.value)
1866
2260
 
1867
2261
  bounds(node.location)
@@ -1920,6 +2314,9 @@ module Prism
1920
2314
  # {}
1921
2315
  # ^^
1922
2316
  def visit_hash_node(node)
2317
+ bounds(node.opening_loc)
2318
+ on_lbrace("{")
2319
+
1923
2320
  elements =
1924
2321
  if node.elements.any?
1925
2322
  args = visit_all(node.elements)
@@ -1928,6 +2325,8 @@ module Prism
1928
2325
  on_assoclist_from_args(args)
1929
2326
  end
1930
2327
 
2328
+ bounds(node.closing_loc)
2329
+ on_rbrace("}")
1931
2330
  bounds(node.location)
1932
2331
  on_hash(elements)
1933
2332
  end
@@ -1936,6 +2335,15 @@ module Prism
1936
2335
  # ^^
1937
2336
  def visit_hash_pattern_node(node)
1938
2337
  constant = visit(node.constant)
2338
+
2339
+ if node.constant
2340
+ bounds(node.opening_loc)
2341
+ node.opening == "[" ? on_lbracket("[") : on_lparen("(")
2342
+ elsif node.opening_loc
2343
+ bounds(node.opening_loc)
2344
+ on_lbrace("{")
2345
+ end
2346
+
1939
2347
  elements =
1940
2348
  if node.elements.any? || !node.rest.nil?
1941
2349
  node.elements.map do |element|
@@ -1943,8 +2351,8 @@ module Prism
1943
2351
  if (key = element.key).opening_loc.nil?
1944
2352
  visit(key)
1945
2353
  else
1946
- bounds(key.value_loc)
1947
- if (value = key.value).empty?
2354
+ bounds(key.content_loc)
2355
+ if (value = key.content).empty?
1948
2356
  on_string_content
1949
2357
  else
1950
2358
  on_string_add(on_string_content, on_tstring_content(value))
@@ -1958,12 +2366,21 @@ module Prism
1958
2366
  rest =
1959
2367
  case node.rest
1960
2368
  when AssocSplatNode
2369
+ bounds(node.rest.operator_loc)
2370
+ on_op("**")
1961
2371
  visit(node.rest.value)
1962
2372
  when NoKeywordsParameterNode
1963
2373
  bounds(node.rest.location)
1964
2374
  on_var_field(visit(node.rest))
1965
2375
  end
1966
2376
 
2377
+ if node.constant
2378
+ bounds(node.closing_loc)
2379
+ node.closing == "]" ? on_rbracket("]") : on_rparen(")")
2380
+ elsif node.closing_loc
2381
+ bounds(node.closing_loc)
2382
+ on_rbrace("}")
2383
+ end
1967
2384
  bounds(node.location)
1968
2385
  on_hshptn(constant, elements, rest)
1969
2386
  end
@@ -1979,13 +2396,27 @@ module Prism
1979
2396
  def visit_if_node(node)
1980
2397
  if node.then_keyword == "?"
1981
2398
  predicate = visit(node.predicate)
2399
+
2400
+ bounds(node.then_keyword_loc)
2401
+ on_op("?")
2402
+
1982
2403
  truthy = visit(node.statements.body.first)
2404
+
2405
+ bounds(node.subsequent.else_keyword_loc)
2406
+ on_op(":")
2407
+
1983
2408
  falsy = visit(node.subsequent.statements.body.first)
1984
2409
 
1985
2410
  bounds(node.location)
1986
2411
  on_ifop(predicate, truthy, falsy)
1987
2412
  elsif node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
2413
+ bounds(node.if_keyword_loc)
2414
+ on_kw(node.if_keyword)
1988
2415
  predicate = visit(node.predicate)
2416
+ if node.then_keyword_loc && node.then_keyword != "?"
2417
+ bounds(node.then_keyword_loc)
2418
+ on_kw("then")
2419
+ end
1989
2420
  statements =
1990
2421
  if node.statements.nil?
1991
2422
  bounds(node.location)
@@ -1995,6 +2426,11 @@ module Prism
1995
2426
  end
1996
2427
  subsequent = visit(node.subsequent)
1997
2428
 
2429
+ if node.end_keyword_loc && !node.subsequent
2430
+ bounds(node.end_keyword_loc)
2431
+ on_kw("end")
2432
+ end
2433
+
1998
2434
  bounds(node.location)
1999
2435
  if node.if_keyword == "if"
2000
2436
  on_if(predicate, statements, subsequent)
@@ -2003,6 +2439,8 @@ module Prism
2003
2439
  end
2004
2440
  else
2005
2441
  statements = visit(node.statements.body.first)
2442
+ bounds(node.if_keyword_loc)
2443
+ on_kw(node.if_keyword)
2006
2444
  predicate = visit(node.predicate)
2007
2445
 
2008
2446
  bounds(node.location)
@@ -2034,7 +2472,14 @@ module Prism
2034
2472
  # This is a special case where we're not going to call on_in directly
2035
2473
  # because we don't have access to the subsequent. Instead, we'll return
2036
2474
  # the component parts and let the parent node handle it.
2475
+ bounds(node.in_keyword_loc)
2476
+ on_kw("in")
2477
+
2037
2478
  pattern = visit_pattern_node(node.pattern)
2479
+ if node.then_keyword_loc
2480
+ bounds(node.then_keyword_loc)
2481
+ on_kw("then")
2482
+ end
2038
2483
  statements =
2039
2484
  if node.statements.nil?
2040
2485
  bounds(node.location)
@@ -2050,7 +2495,14 @@ module Prism
2050
2495
  # ^^^^^^^^^^^^^^^
2051
2496
  def visit_index_operator_write_node(node)
2052
2497
  receiver = visit(node.receiver)
2053
- arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2498
+
2499
+ bounds(node.opening_loc)
2500
+ on_lbracket("[")
2501
+
2502
+ arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2503
+
2504
+ bounds(node.closing_loc)
2505
+ on_rbracket("]")
2054
2506
 
2055
2507
  bounds(node.location)
2056
2508
  target = on_aref_field(receiver, arguments)
@@ -2067,7 +2519,14 @@ module Prism
2067
2519
  # ^^^^^^^^^^^^^^^^
2068
2520
  def visit_index_and_write_node(node)
2069
2521
  receiver = visit(node.receiver)
2070
- arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2522
+
2523
+ bounds(node.opening_loc)
2524
+ on_lbracket("[")
2525
+
2526
+ arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2527
+
2528
+ bounds(node.closing_loc)
2529
+ on_rbracket("]")
2071
2530
 
2072
2531
  bounds(node.location)
2073
2532
  target = on_aref_field(receiver, arguments)
@@ -2084,7 +2543,14 @@ module Prism
2084
2543
  # ^^^^^^^^^^^^^^^^
2085
2544
  def visit_index_or_write_node(node)
2086
2545
  receiver = visit(node.receiver)
2087
- arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2546
+
2547
+ bounds(node.opening_loc)
2548
+ on_lbracket("[")
2549
+
2550
+ arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2551
+
2552
+ bounds(node.closing_loc)
2553
+ on_rbracket("]")
2088
2554
 
2089
2555
  bounds(node.location)
2090
2556
  target = on_aref_field(receiver, arguments)
@@ -2101,7 +2567,14 @@ module Prism
2101
2567
  # ^^^^^^^^
2102
2568
  def visit_index_target_node(node)
2103
2569
  receiver = visit(node.receiver)
2104
- arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2570
+
2571
+ bounds(node.opening_loc)
2572
+ on_lbracket("[")
2573
+
2574
+ arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
2575
+
2576
+ bounds(node.closing_loc)
2577
+ on_rbracket("]")
2105
2578
 
2106
2579
  bounds(node.location)
2107
2580
  on_aref_field(receiver, arguments)
@@ -2119,6 +2592,10 @@ module Prism
2119
2592
  def visit_instance_variable_write_node(node)
2120
2593
  bounds(node.name_loc)
2121
2594
  target = on_var_field(on_ivar(node.name.to_s))
2595
+
2596
+ bounds(node.operator_loc)
2597
+ on_op("=")
2598
+
2122
2599
  value = visit_write_value(node.value)
2123
2600
 
2124
2601
  bounds(node.location)
@@ -2221,20 +2698,37 @@ module Prism
2221
2698
  # "foo #{bar}"
2222
2699
  # ^^^^^^^^^^^^
2223
2700
  def visit_interpolated_string_node(node)
2224
- if node.opening&.start_with?("<<~")
2225
- heredoc = visit_heredoc_string_node(node)
2701
+ with_string_bounds(node) do
2702
+ if node.opening&.start_with?("<<~")
2703
+ heredoc = visit_heredoc_string_node(node)
2226
2704
 
2227
- bounds(node.location)
2228
- on_string_literal(heredoc)
2229
- elsif !node.heredoc? && node.parts.length > 1 && node.parts.any? { |part| (part.is_a?(StringNode) || part.is_a?(InterpolatedStringNode)) && !part.opening_loc.nil? }
2230
- first, *rest = node.parts
2231
- rest.inject(visit(first)) do |content, part|
2232
- concat = visit(part)
2705
+ bounds(node.location)
2706
+ on_string_literal(heredoc)
2707
+ elsif !node.heredoc? && node.parts.length > 1 && node.parts.any? { |part| (part.is_a?(StringNode) || part.is_a?(InterpolatedStringNode)) && !part.opening_loc.nil? }
2708
+ first, *rest = node.parts
2709
+ rest.inject(visit(first)) do |content, part|
2710
+ concat = visit(part)
2711
+
2712
+ bounds(part.location)
2713
+ on_string_concat(content, concat)
2714
+ end
2715
+ else
2716
+ bounds(node.parts.first.location)
2717
+ parts =
2718
+ node.parts.inject(on_string_content) do |content, part|
2719
+ on_string_add(content, visit_string_content(part))
2720
+ end
2233
2721
 
2234
- bounds(part.location)
2235
- on_string_concat(content, concat)
2722
+ bounds(node.location)
2723
+ on_string_literal(parts)
2236
2724
  end
2237
- else
2725
+ end
2726
+ end
2727
+
2728
+ # :"foo #{bar}"
2729
+ # ^^^^^^^^^^^^^
2730
+ def visit_interpolated_symbol_node(node)
2731
+ with_string_bounds(node) do
2238
2732
  bounds(node.parts.first.location)
2239
2733
  parts =
2240
2734
  node.parts.inject(on_string_content) do |content, part|
@@ -2242,40 +2736,29 @@ module Prism
2242
2736
  end
2243
2737
 
2244
2738
  bounds(node.location)
2245
- on_string_literal(parts)
2739
+ on_dyna_symbol(parts)
2246
2740
  end
2247
2741
  end
2248
2742
 
2249
- # :"foo #{bar}"
2250
- # ^^^^^^^^^^^^^
2251
- def visit_interpolated_symbol_node(node)
2252
- bounds(node.parts.first.location)
2253
- parts =
2254
- node.parts.inject(on_string_content) do |content, part|
2255
- on_string_add(content, visit_string_content(part))
2256
- end
2257
-
2258
- bounds(node.location)
2259
- on_dyna_symbol(parts)
2260
- end
2261
-
2262
2743
  # `foo #{bar}`
2263
2744
  # ^^^^^^^^^^^^
2264
2745
  def visit_interpolated_x_string_node(node)
2265
- if node.opening.start_with?("<<~")
2266
- heredoc = visit_heredoc_x_string_node(node)
2746
+ with_string_bounds(node) do
2747
+ if node.opening.start_with?("<<~")
2748
+ heredoc = visit_heredoc_x_string_node(node)
2267
2749
 
2268
- bounds(node.location)
2269
- on_xstring_literal(heredoc)
2270
- else
2271
- bounds(node.parts.first.location)
2272
- parts =
2273
- node.parts.inject(on_xstring_new) do |content, part|
2274
- on_xstring_add(content, visit_string_content(part))
2275
- end
2750
+ bounds(node.location)
2751
+ on_xstring_literal(heredoc)
2752
+ else
2753
+ bounds(node.parts.first.location)
2754
+ parts =
2755
+ node.parts.inject(on_xstring_new) do |content, part|
2756
+ on_xstring_add(content, visit_string_content(part))
2757
+ end
2276
2758
 
2277
- bounds(node.location)
2278
- on_xstring_literal(parts)
2759
+ bounds(node.location)
2760
+ on_xstring_literal(parts)
2761
+ end
2279
2762
  end
2280
2763
  end
2281
2764
 
@@ -2316,6 +2799,9 @@ module Prism
2316
2799
  # def foo(**); end
2317
2800
  # ^^
2318
2801
  def visit_keyword_rest_parameter_node(node)
2802
+ bounds(node.operator_loc)
2803
+ on_op("**")
2804
+
2319
2805
  if node.name_loc.nil?
2320
2806
  bounds(node.location)
2321
2807
  on_kwrest_param(nil)
@@ -2335,6 +2821,11 @@ module Prism
2335
2821
 
2336
2822
  parameters =
2337
2823
  if node.parameters.is_a?(BlockParametersNode)
2824
+ if node.parameters.opening_loc
2825
+ bounds(node.parameters.opening_loc)
2826
+ on_lparen("(")
2827
+ end
2828
+
2338
2829
  # Ripper does not track block-locals within lambdas, so we skip
2339
2830
  # directly to the parameters here.
2340
2831
  params =
@@ -2345,6 +2836,13 @@ module Prism
2345
2836
  visit(node.parameters.parameters)
2346
2837
  end
2347
2838
 
2839
+ visit_all(node.parameters.locals)
2840
+
2841
+ if node.parameters.closing_loc
2842
+ bounds(node.parameters.closing_loc)
2843
+ on_rparen(")")
2844
+ end
2845
+
2348
2846
  if node.parameters.opening_loc.nil?
2349
2847
  params
2350
2848
  else
@@ -2357,9 +2855,11 @@ module Prism
2357
2855
  end
2358
2856
 
2359
2857
  braces = node.opening == "{"
2858
+ bounds(node.opening_loc)
2360
2859
  if braces
2361
- bounds(node.opening_loc)
2362
2860
  on_tlambeg(node.opening)
2861
+ else
2862
+ on_kw("do")
2363
2863
  end
2364
2864
 
2365
2865
  body =
@@ -2372,7 +2872,7 @@ module Prism
2372
2872
  braces ? stmts : on_bodystmt(stmts, nil, nil, nil)
2373
2873
  when StatementsNode
2374
2874
  stmts = node.body.body
2375
- stmts.unshift(nil) if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false)
2875
+ stmts = [nil, *stmts] if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false)
2376
2876
  stmts = visit_statements_node_body(stmts)
2377
2877
 
2378
2878
  bounds(node.body.location)
@@ -2383,6 +2883,13 @@ module Prism
2383
2883
  raise
2384
2884
  end
2385
2885
 
2886
+ bounds(node.closing_loc)
2887
+ if braces
2888
+ on_rbrace("}")
2889
+ else
2890
+ on_kw("end")
2891
+ end
2892
+
2386
2893
  bounds(node.location)
2387
2894
  on_lambda(parameters, body)
2388
2895
  end
@@ -2399,6 +2906,10 @@ module Prism
2399
2906
  def visit_local_variable_write_node(node)
2400
2907
  bounds(node.name_loc)
2401
2908
  target = on_var_field(on_ident(node.name_loc.slice))
2909
+
2910
+ bounds(node.operator_loc)
2911
+ on_op("=")
2912
+
2402
2913
  value = visit_write_value(node.value)
2403
2914
 
2404
2915
  bounds(node.location)
@@ -2473,6 +2984,8 @@ module Prism
2473
2984
  # ^^^^^^^^^^
2474
2985
  def visit_match_predicate_node(node)
2475
2986
  value = visit(node.value)
2987
+ bounds(node.keyword_loc)
2988
+ on_kw("in")
2476
2989
  pattern = on_in(visit_pattern_node(node.pattern), nil, nil)
2477
2990
 
2478
2991
  on_case(value, pattern)
@@ -2482,6 +2995,10 @@ module Prism
2482
2995
  # ^^^^^^^^^^
2483
2996
  def visit_match_required_node(node)
2484
2997
  value = visit(node.value)
2998
+
2999
+ bounds(node.operator_loc)
3000
+ on_op("=>")
3001
+
2485
3002
  pattern = on_in(visit_pattern_node(node.pattern), nil, nil)
2486
3003
 
2487
3004
  on_case(value, pattern)
@@ -2495,13 +3012,16 @@ module Prism
2495
3012
 
2496
3013
  # A node that is missing from the syntax tree. This is only used in the
2497
3014
  # case of a syntax error.
2498
- def visit_missing_node(node)
2499
- raise "Cannot visit missing nodes directly."
3015
+ def visit_error_recovery_node(node)
3016
+ raise "Cannot visit error recovery nodes directly."
2500
3017
  end
2501
3018
 
2502
3019
  # module Foo; end
2503
3020
  # ^^^^^^^^^^^^^^^
2504
3021
  def visit_module_node(node)
3022
+ bounds(node.module_keyword_loc)
3023
+ on_kw("module")
3024
+
2505
3025
  constant_path =
2506
3026
  if node.constant_path.is_a?(ConstantReadNode)
2507
3027
  bounds(node.constant_path.location)
@@ -2512,6 +3032,9 @@ module Prism
2512
3032
 
2513
3033
  bodystmt = visit_body_node(node.constant_path.location, node.body, true)
2514
3034
 
3035
+ bounds(node.end_keyword_loc)
3036
+ on_kw("end")
3037
+
2515
3038
  bounds(node.location)
2516
3039
  on_module(constant_path, bodystmt)
2517
3040
  end
@@ -2519,9 +3042,19 @@ module Prism
2519
3042
  # (foo, bar), bar = qux
2520
3043
  # ^^^^^^^^^^
2521
3044
  def visit_multi_target_node(node)
3045
+ if node.lparen_loc
3046
+ bounds(node.lparen_loc)
3047
+ on_lparen("(")
3048
+ end
3049
+
2522
3050
  bounds(node.location)
2523
3051
  targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, true)
2524
3052
 
3053
+ if node.rparen_loc
3054
+ bounds(node.rparen_loc)
3055
+ on_rparen(")")
3056
+ end
3057
+
2525
3058
  if node.lparen_loc.nil?
2526
3059
  targets
2527
3060
  else
@@ -2573,9 +3106,22 @@ module Prism
2573
3106
  # foo, bar = baz
2574
3107
  # ^^^^^^^^^^^^^^
2575
3108
  def visit_multi_write_node(node)
3109
+ if node.lparen_loc
3110
+ bounds(node.lparen_loc)
3111
+ on_lparen("(")
3112
+ end
3113
+
2576
3114
  bounds(node.location)
2577
3115
  targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, true)
2578
3116
 
3117
+ if node.rparen_loc
3118
+ bounds(node.rparen_loc)
3119
+ on_rparen(")")
3120
+ end
3121
+
3122
+ bounds(node.operator_loc)
3123
+ on_op("=")
3124
+
2579
3125
  unless node.lparen_loc.nil?
2580
3126
  bounds(node.lparen_loc)
2581
3127
  targets = on_mlhs_paren(targets)
@@ -2593,6 +3139,9 @@ module Prism
2593
3139
  # next foo
2594
3140
  # ^^^^^^^^
2595
3141
  def visit_next_node(node)
3142
+ bounds(node.keyword_loc)
3143
+ on_kw("next")
3144
+
2596
3145
  if node.arguments.nil?
2597
3146
  bounds(node.location)
2598
3147
  on_next(on_args_new)
@@ -2611,9 +3160,24 @@ module Prism
2611
3160
  on_var_ref(on_kw("nil"))
2612
3161
  end
2613
3162
 
3163
+ # def foo(&nil); end
3164
+ # ^^^^
3165
+ def visit_no_block_parameter_node(node)
3166
+ bounds(node.operator_loc)
3167
+ on_op("&")
3168
+ bounds(node.keyword_loc)
3169
+ on_kw("nil")
3170
+ bounds(node.location)
3171
+ on_blockarg(:nil)
3172
+ end
3173
+
2614
3174
  # def foo(**nil); end
2615
3175
  # ^^^^^
2616
3176
  def visit_no_keywords_parameter_node(node)
3177
+ bounds(node.operator_loc)
3178
+ on_op("**")
3179
+ bounds(node.keyword_loc)
3180
+ on_kw("nil")
2617
3181
  bounds(node.location)
2618
3182
  on_nokw_param(nil)
2619
3183
 
@@ -2646,7 +3210,11 @@ module Prism
2646
3210
  # ^^^^^^^
2647
3211
  def visit_optional_parameter_node(node)
2648
3212
  bounds(node.name_loc)
2649
- name = visit_token(node.name.to_s)
3213
+ name = on_ident(node.name.to_s)
3214
+
3215
+ bounds(node.operator_loc)
3216
+ on_op("=")
3217
+
2650
3218
  value = visit(node.value)
2651
3219
 
2652
3220
  [name, value]
@@ -2656,6 +3224,14 @@ module Prism
2656
3224
  # ^^^^^^
2657
3225
  def visit_or_node(node)
2658
3226
  left = visit(node.left)
3227
+
3228
+ bounds(node.operator_loc)
3229
+ if node.operator == "or"
3230
+ on_kw("or")
3231
+ else
3232
+ on_op("||")
3233
+ end
3234
+
2659
3235
  right = visit(node.right)
2660
3236
 
2661
3237
  bounds(node.location)
@@ -2679,9 +3255,19 @@ module Prism
2679
3255
 
2680
3256
  # Visit a destructured positional parameter node.
2681
3257
  private def visit_destructured_parameter_node(node)
3258
+ if node.lparen_loc
3259
+ bounds(node.lparen_loc)
3260
+ on_lparen("(")
3261
+ end
3262
+
2682
3263
  bounds(node.location)
2683
3264
  targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, false)
2684
3265
 
3266
+ if node.rparen_loc
3267
+ bounds(node.rparen_loc)
3268
+ on_rparen(")")
3269
+ end
3270
+
2685
3271
  bounds(node.lparen_loc)
2686
3272
  on_mlhs_paren(targets)
2687
3273
  end
@@ -2692,6 +3278,9 @@ module Prism
2692
3278
  # (1)
2693
3279
  # ^^^
2694
3280
  def visit_parentheses_node(node)
3281
+ bounds(node.opening_loc)
3282
+ on_lparen("(")
3283
+
2695
3284
  body =
2696
3285
  if node.body.nil?
2697
3286
  on_stmts_add(on_stmts_new, on_void_stmt)
@@ -2699,6 +3288,8 @@ module Prism
2699
3288
  visit(node.body)
2700
3289
  end
2701
3290
 
3291
+ bounds(node.closing_loc)
3292
+ on_rparen(")")
2702
3293
  bounds(node.location)
2703
3294
  on_paren(body)
2704
3295
  end
@@ -2706,8 +3297,15 @@ module Prism
2706
3297
  # foo => ^(bar)
2707
3298
  # ^^^^^^
2708
3299
  def visit_pinned_expression_node(node)
3300
+ bounds(node.operator_loc)
3301
+ on_op("^")
3302
+ bounds(node.lparen_loc)
3303
+ on_lparen("(")
3304
+
2709
3305
  expression = visit(node.expression)
2710
3306
 
3307
+ bounds(node.rparen_loc)
3308
+ on_rparen(")")
2711
3309
  bounds(node.location)
2712
3310
  on_begin(expression)
2713
3311
  end
@@ -2715,12 +3313,20 @@ module Prism
2715
3313
  # foo = 1 and bar => ^foo
2716
3314
  # ^^^^
2717
3315
  def visit_pinned_variable_node(node)
3316
+ bounds(node.operator_loc)
3317
+ on_op("^")
3318
+
2718
3319
  visit(node.variable)
2719
3320
  end
2720
3321
 
2721
3322
  # END {}
2722
3323
  # ^^^^^^
2723
3324
  def visit_post_execution_node(node)
3325
+ bounds(node.keyword_loc)
3326
+ on_kw("END")
3327
+ bounds(node.opening_loc)
3328
+ on_lbrace("{")
3329
+
2724
3330
  statements =
2725
3331
  if node.statements.nil?
2726
3332
  bounds(node.location)
@@ -2729,6 +3335,8 @@ module Prism
2729
3335
  visit(node.statements)
2730
3336
  end
2731
3337
 
3338
+ bounds(node.closing_loc)
3339
+ on_rbrace("}")
2732
3340
  bounds(node.location)
2733
3341
  on_END(statements)
2734
3342
  end
@@ -2736,6 +3344,11 @@ module Prism
2736
3344
  # BEGIN {}
2737
3345
  # ^^^^^^^^
2738
3346
  def visit_pre_execution_node(node)
3347
+ bounds(node.keyword_loc)
3348
+ on_kw("BEGIN")
3349
+ bounds(node.opening_loc)
3350
+ on_lbrace("{")
3351
+
2739
3352
  statements =
2740
3353
  if node.statements.nil?
2741
3354
  bounds(node.location)
@@ -2744,6 +3357,8 @@ module Prism
2744
3357
  visit(node.statements)
2745
3358
  end
2746
3359
 
3360
+ bounds(node.closing_loc)
3361
+ on_rbrace("}")
2747
3362
  bounds(node.location)
2748
3363
  on_BEGIN(statements)
2749
3364
  end
@@ -2751,7 +3366,7 @@ module Prism
2751
3366
  # The top-level program node.
2752
3367
  def visit_program_node(node)
2753
3368
  body = node.statements.body
2754
- body << nil if body.empty?
3369
+ body = [nil] if body.empty?
2755
3370
  statements = visit_statements_node_body(body)
2756
3371
 
2757
3372
  bounds(node.location)
@@ -2762,6 +3377,10 @@ module Prism
2762
3377
  # ^^^^
2763
3378
  def visit_range_node(node)
2764
3379
  left = visit(node.left)
3380
+
3381
+ bounds(node.operator_loc)
3382
+ on_op(node.operator)
3383
+
2765
3384
  right = visit(node.right)
2766
3385
 
2767
3386
  bounds(node.location)
@@ -2782,6 +3401,7 @@ module Prism
2782
3401
  # ^^^^
2783
3402
  def visit_redo_node(node)
2784
3403
  bounds(node.location)
3404
+ on_kw("redo")
2785
3405
  on_redo
2786
3406
  end
2787
3407
 
@@ -2824,6 +3444,9 @@ module Prism
2824
3444
  # foo rescue bar
2825
3445
  # ^^^^^^^^^^^^^^
2826
3446
  def visit_rescue_modifier_node(node)
3447
+ bounds(node.keyword_loc)
3448
+ on_kw("rescue")
3449
+
2827
3450
  expression = visit_write_value(node.expression)
2828
3451
  rescue_expression = visit(node.rescue_expression)
2829
3452
 
@@ -2834,6 +3457,9 @@ module Prism
2834
3457
  # begin; rescue; end
2835
3458
  # ^^^^^^^
2836
3459
  def visit_rescue_node(node)
3460
+ bounds(node.keyword_loc)
3461
+ on_kw("rescue")
3462
+
2837
3463
  exceptions =
2838
3464
  case node.exceptions.length
2839
3465
  when 0
@@ -2871,6 +3497,11 @@ module Prism
2871
3497
  end
2872
3498
  end
2873
3499
 
3500
+ if node.operator_loc
3501
+ bounds(node.operator_loc)
3502
+ on_op("=>")
3503
+ end
3504
+
2874
3505
  reference = visit(node.reference)
2875
3506
  statements =
2876
3507
  if node.statements.nil?
@@ -2892,12 +3523,15 @@ module Prism
2892
3523
  # def foo(*); end
2893
3524
  # ^
2894
3525
  def visit_rest_parameter_node(node)
3526
+ bounds(node.operator_loc)
3527
+ on_op("*")
3528
+
2895
3529
  if node.name_loc.nil?
2896
3530
  bounds(node.location)
2897
3531
  on_rest_param(nil)
2898
3532
  else
2899
3533
  bounds(node.name_loc)
2900
- on_rest_param(visit_token(node.name.to_s))
3534
+ on_rest_param(on_ident(node.name.to_s))
2901
3535
  end
2902
3536
  end
2903
3537
 
@@ -2905,6 +3539,7 @@ module Prism
2905
3539
  # ^^^^^
2906
3540
  def visit_retry_node(node)
2907
3541
  bounds(node.location)
3542
+ on_kw("retry")
2908
3543
  on_retry
2909
3544
  end
2910
3545
 
@@ -2914,6 +3549,9 @@ module Prism
2914
3549
  # return 1
2915
3550
  # ^^^^^^^^
2916
3551
  def visit_return_node(node)
3552
+ bounds(node.keyword_loc)
3553
+ on_kw("return")
3554
+
2917
3555
  if node.arguments.nil?
2918
3556
  bounds(node.location)
2919
3557
  on_return0
@@ -2940,9 +3578,17 @@ module Prism
2940
3578
  # class << self; end
2941
3579
  # ^^^^^^^^^^^^^^^^^^
2942
3580
  def visit_singleton_class_node(node)
3581
+ bounds(node.class_keyword_loc)
3582
+ on_kw("class")
3583
+ bounds(node.operator_loc)
3584
+ on_op("<<")
3585
+
2943
3586
  expression = visit(node.expression)
2944
3587
  bodystmt = visit_body_node(node.body&.location || node.end_keyword_loc, node.body)
2945
3588
 
3589
+ bounds(node.end_keyword_loc)
3590
+ on_kw("end")
3591
+
2946
3592
  bounds(node.location)
2947
3593
  on_sclass(expression, bodystmt)
2948
3594
  end
@@ -2977,6 +3623,8 @@ module Prism
2977
3623
  # def foo(*); bar(*); end
2978
3624
  # ^
2979
3625
  def visit_splat_node(node)
3626
+ bounds(node.operator_loc)
3627
+ on_op("*")
2980
3628
  visit(node.expression)
2981
3629
  end
2982
3630
 
@@ -2999,24 +3647,66 @@ module Prism
2999
3647
  # "foo"
3000
3648
  # ^^^^^
3001
3649
  def visit_string_node(node)
3002
- if (content = node.content).empty?
3003
- bounds(node.location)
3004
- on_string_literal(on_string_content)
3005
- elsif (opening = node.opening) == "?"
3006
- bounds(node.location)
3007
- on_CHAR("?#{node.content}")
3008
- elsif opening.start_with?("<<~")
3009
- heredoc = visit_heredoc_string_node(node.to_interpolated)
3650
+ with_string_bounds(node) do
3651
+ if (content = node.content).empty?
3652
+ bounds(node.location)
3653
+ on_string_literal(on_string_content)
3654
+ elsif (opening = node.opening) == "?"
3655
+ bounds(node.location)
3656
+ on_CHAR("?#{node.content}")
3657
+ elsif opening.start_with?("<<~")
3658
+ heredoc = visit_heredoc_string_node(node.to_interpolated)
3010
3659
 
3011
- bounds(node.location)
3012
- on_string_literal(heredoc)
3013
- else
3014
- bounds(node.content_loc)
3015
- tstring_content = on_tstring_content(content)
3660
+ bounds(node.location)
3661
+ on_string_literal(heredoc)
3662
+ else
3663
+ bounds(node.content_loc)
3664
+ tstring_content = on_tstring_content(content)
3016
3665
 
3017
- bounds(node.location)
3018
- on_string_literal(on_string_add(on_string_content, tstring_content))
3666
+ bounds(node.location)
3667
+ on_string_literal(on_string_add(on_string_content, tstring_content))
3668
+ end
3669
+ end
3670
+ end
3671
+
3672
+ # Responsible for emitting the various string-like begin/end events
3673
+ private def with_string_bounds(node)
3674
+ # `foo "bar": baz` doesn't emit the closing location
3675
+ assoc = !(opening = node.opening)&.include?(":") && node.closing&.end_with?(":")
3676
+
3677
+ is_heredoc = opening&.start_with?("<<")
3678
+ if is_heredoc
3679
+ bounds(node.opening_loc)
3680
+ on_heredoc_beg(node.opening)
3681
+ elsif opening&.start_with?(":", "%s")
3682
+ bounds(node.opening_loc)
3683
+ on_symbeg(node.opening)
3684
+ elsif opening&.start_with?("`", "%x")
3685
+ bounds(node.opening_loc)
3686
+ on_backtick(node.opening)
3687
+ elsif opening && !opening.start_with?("?")
3688
+ bounds(node.opening_loc)
3689
+ on_tstring_beg(opening)
3690
+ end
3691
+
3692
+ result = yield
3693
+ if assoc
3694
+ if node.closing != ":"
3695
+ bounds(node.closing_loc)
3696
+ on_label_end(node.closing)
3697
+ end
3698
+ return result
3699
+ end
3700
+
3701
+ if is_heredoc
3702
+ bounds(node.closing_loc)
3703
+ on_heredoc_end(node.closing)
3704
+ elsif node.closing_loc
3705
+ bounds(node.closing_loc)
3706
+ on_tstring_end(node.closing)
3019
3707
  end
3708
+
3709
+ result
3020
3710
  end
3021
3711
 
3022
3712
  # Ripper gives back the escaped string content but strips out the common
@@ -3096,42 +3786,39 @@ module Prism
3096
3786
 
3097
3787
  # Visit a heredoc node that is representing a string.
3098
3788
  private def visit_heredoc_string_node(node)
3099
- bounds(node.opening_loc)
3100
- on_heredoc_beg(node.opening)
3101
-
3102
3789
  bounds(node.location)
3103
- result =
3104
- visit_heredoc_node(node.parts, on_string_content) do |parts, part|
3105
- on_string_add(parts, part)
3106
- end
3107
-
3108
- bounds(node.closing_loc)
3109
- on_heredoc_end(node.closing)
3110
-
3111
- result
3790
+ visit_heredoc_node(node.parts, on_string_content) do |parts, part|
3791
+ on_string_add(parts, part)
3792
+ end
3112
3793
  end
3113
3794
 
3114
3795
  # Visit a heredoc node that is representing an xstring.
3115
3796
  private def visit_heredoc_x_string_node(node)
3116
- bounds(node.opening_loc)
3117
- on_heredoc_beg(node.opening)
3118
-
3119
3797
  bounds(node.location)
3120
- result =
3121
- visit_heredoc_node(node.parts, on_xstring_new) do |parts, part|
3122
- on_xstring_add(parts, part)
3123
- end
3124
-
3125
- bounds(node.closing_loc)
3126
- on_heredoc_end(node.closing)
3127
-
3128
- result
3798
+ visit_heredoc_node(node.parts, on_xstring_new) do |parts, part|
3799
+ on_xstring_add(parts, part)
3800
+ end
3129
3801
  end
3130
3802
 
3131
3803
  # super(foo)
3132
3804
  # ^^^^^^^^^^
3133
3805
  def visit_super_node(node)
3134
- arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location))
3806
+ bounds(node.keyword_loc)
3807
+ on_kw("super")
3808
+
3809
+ if node.lparen_loc
3810
+ bounds(node.lparen_loc)
3811
+ on_lparen("(")
3812
+ end
3813
+
3814
+ arguments, block_node = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location))
3815
+
3816
+ if node.rparen_loc
3817
+ bounds(node.rparen_loc)
3818
+ on_rparen(")")
3819
+ end
3820
+
3821
+ block = visit(block_node)
3135
3822
 
3136
3823
  if !node.lparen_loc.nil?
3137
3824
  bounds(node.lparen_loc)
@@ -3141,7 +3828,7 @@ module Prism
3141
3828
  bounds(node.location)
3142
3829
  call = on_super(arguments)
3143
3830
 
3144
- if has_ripper_block
3831
+ if block_node
3145
3832
  bounds(node.block.location)
3146
3833
  on_method_add_block(call, block)
3147
3834
  else
@@ -3152,24 +3839,27 @@ module Prism
3152
3839
  # :foo
3153
3840
  # ^^^^
3154
3841
  def visit_symbol_node(node)
3155
- if (opening = node.opening)&.match?(/^%s|['"]:?$/)
3156
- bounds(node.value_loc)
3157
- content = on_string_content
3842
+ with_string_bounds(node) do
3843
+ if (opening = node.opening)&.match?(/^%s|['"]:?$/)
3844
+ bounds(node.content_loc)
3845
+ content = on_string_content
3158
3846
 
3159
- if !(value = node.value).empty?
3160
- content = on_string_add(content, on_tstring_content(value))
3161
- end
3847
+ if !(value = node.content).empty?
3848
+ content = on_string_add(content, on_tstring_content(value))
3849
+ end
3162
3850
 
3163
- on_dyna_symbol(content)
3164
- elsif (closing = node.closing) == ":"
3165
- bounds(node.location)
3166
- on_label("#{node.value}:")
3167
- elsif opening.nil? && node.closing_loc.nil?
3168
- bounds(node.value_loc)
3169
- on_symbol_literal(visit_token(node.value))
3170
- else
3171
- bounds(node.value_loc)
3172
- on_symbol_literal(on_symbol(visit_token(node.value)))
3851
+ bounds(node.location)
3852
+ on_dyna_symbol(content)
3853
+ elsif (closing = node.closing) == ":"
3854
+ bounds(node.location)
3855
+ on_label("#{node.content}:")
3856
+ elsif opening.nil? && node.closing_loc.nil?
3857
+ bounds(node.content_loc)
3858
+ on_symbol_literal(visit_token(node.content))
3859
+ else
3860
+ bounds(node.content_loc)
3861
+ on_symbol_literal(on_symbol(visit_token(node.content)))
3862
+ end
3173
3863
  end
3174
3864
  end
3175
3865
 
@@ -3183,6 +3873,9 @@ module Prism
3183
3873
  # undef foo
3184
3874
  # ^^^^^^^^^
3185
3875
  def visit_undef_node(node)
3876
+ bounds(node.keyword_loc)
3877
+ on_kw("undef")
3878
+
3186
3879
  names = visit_all(node.names)
3187
3880
 
3188
3881
  bounds(node.location)
@@ -3196,7 +3889,13 @@ module Prism
3196
3889
  # ^^^^^^^^^^^^^^
3197
3890
  def visit_unless_node(node)
3198
3891
  if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
3892
+ bounds(node.unless_keyword_loc)
3893
+ on_kw("unless")
3199
3894
  predicate = visit(node.predicate)
3895
+ if node.then_keyword_loc
3896
+ bounds(node.then_keyword_loc)
3897
+ on_kw("then")
3898
+ end
3200
3899
  statements =
3201
3900
  if node.statements.nil?
3202
3901
  bounds(node.location)
@@ -3206,10 +3905,17 @@ module Prism
3206
3905
  end
3207
3906
  else_clause = visit(node.else_clause)
3208
3907
 
3908
+ if node.end_keyword_loc && !node.else_clause
3909
+ bounds(node.end_keyword_loc)
3910
+ on_kw("end")
3911
+ end
3912
+
3209
3913
  bounds(node.location)
3210
3914
  on_unless(predicate, statements, else_clause)
3211
3915
  else
3212
3916
  statements = visit(node.statements.body.first)
3917
+ bounds(node.unless_keyword_loc)
3918
+ on_kw("unless")
3213
3919
  predicate = visit(node.predicate)
3214
3920
 
3215
3921
  bounds(node.location)
@@ -3223,7 +3929,14 @@ module Prism
3223
3929
  # bar until foo
3224
3930
  # ^^^^^^^^^^^^^
3225
3931
  def visit_until_node(node)
3932
+ bounds(node.until_keyword_loc)
3933
+ on_kw("until")
3934
+
3226
3935
  if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
3936
+ if node.do_keyword_loc
3937
+ bounds(node.do_keyword_loc)
3938
+ on_kw("do")
3939
+ end
3227
3940
  predicate = visit(node.predicate)
3228
3941
  statements =
3229
3942
  if node.statements.nil?
@@ -3233,6 +3946,11 @@ module Prism
3233
3946
  visit(node.statements)
3234
3947
  end
3235
3948
 
3949
+ if node.end_keyword_loc
3950
+ bounds(node.end_keyword_loc)
3951
+ on_kw("end")
3952
+ end
3953
+
3236
3954
  bounds(node.location)
3237
3955
  on_until(predicate, statements)
3238
3956
  else
@@ -3250,7 +3968,14 @@ module Prism
3250
3968
  # This is a special case where we're not going to call on_when directly
3251
3969
  # because we don't have access to the subsequent. Instead, we'll return
3252
3970
  # the component parts and let the parent node handle it.
3971
+ bounds(node.when_keyword_loc)
3972
+ on_kw("when")
3973
+
3253
3974
  conditions = visit_arguments(node.conditions)
3975
+ if node.then_keyword_loc
3976
+ bounds(node.then_keyword_loc)
3977
+ on_kw("then")
3978
+ end
3254
3979
  statements =
3255
3980
  if node.statements.nil?
3256
3981
  bounds(node.location)
@@ -3269,7 +3994,17 @@ module Prism
3269
3994
  # ^^^^^^^^^^^^^
3270
3995
  def visit_while_node(node)
3271
3996
  if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset)
3997
+ bounds(node.while_keyword_loc)
3998
+ on_kw("while")
3999
+ if node.do_keyword_loc
4000
+ bounds(node.do_keyword_loc)
4001
+ on_kw("do")
4002
+ end
3272
4003
  predicate = visit(node.predicate)
4004
+ if node.end_keyword_loc
4005
+ bounds(node.end_keyword_loc)
4006
+ on_kw("end")
4007
+ end
3273
4008
  statements =
3274
4009
  if node.statements.nil?
3275
4010
  bounds(node.location)
@@ -3282,6 +4017,8 @@ module Prism
3282
4017
  on_while(predicate, statements)
3283
4018
  else
3284
4019
  statements = visit(node.statements.body.first)
4020
+ bounds(node.while_keyword_loc)
4021
+ on_kw("while")
3285
4022
  predicate = visit(node.predicate)
3286
4023
 
3287
4024
  bounds(node.location)
@@ -3292,20 +4029,22 @@ module Prism
3292
4029
  # `foo`
3293
4030
  # ^^^^^
3294
4031
  def visit_x_string_node(node)
3295
- if node.unescaped.empty?
3296
- bounds(node.location)
3297
- on_xstring_literal(on_xstring_new)
3298
- elsif node.opening.start_with?("<<~")
3299
- heredoc = visit_heredoc_x_string_node(node.to_interpolated)
4032
+ with_string_bounds(node) do
4033
+ if node.unescaped.empty?
4034
+ bounds(node.location)
4035
+ on_xstring_literal(on_xstring_new)
4036
+ elsif node.opening.start_with?("<<~")
4037
+ heredoc = visit_heredoc_x_string_node(node.to_interpolated)
3300
4038
 
3301
- bounds(node.location)
3302
- on_xstring_literal(heredoc)
3303
- else
3304
- bounds(node.content_loc)
3305
- content = on_tstring_content(node.content)
4039
+ bounds(node.location)
4040
+ on_xstring_literal(heredoc)
4041
+ else
4042
+ bounds(node.content_loc)
4043
+ content = on_tstring_content(node.content)
3306
4044
 
3307
- bounds(node.location)
3308
- on_xstring_literal(on_xstring_add(on_xstring_new, content))
4045
+ bounds(node.location)
4046
+ on_xstring_literal(on_xstring_add(on_xstring_new, content))
4047
+ end
3309
4048
  end
3310
4049
  end
3311
4050
 
@@ -3315,10 +4054,18 @@ module Prism
3315
4054
  # yield 1
3316
4055
  # ^^^^^^^
3317
4056
  def visit_yield_node(node)
4057
+ bounds(node.keyword_loc)
4058
+ on_kw("yield")
4059
+
3318
4060
  if node.arguments.nil? && node.lparen_loc.nil?
3319
4061
  bounds(node.location)
3320
4062
  on_yield0
3321
4063
  else
4064
+ if node.lparen_loc
4065
+ bounds(node.lparen_loc)
4066
+ on_lparen("(")
4067
+ end
4068
+
3322
4069
  arguments =
3323
4070
  if node.arguments.nil?
3324
4071
  bounds(node.location)
@@ -3328,6 +4075,8 @@ module Prism
3328
4075
  end
3329
4076
 
3330
4077
  unless node.lparen_loc.nil?
4078
+ bounds(node.rparen_loc)
4079
+ on_rparen(")")
3331
4080
  bounds(node.lparen_loc)
3332
4081
  arguments = on_paren(arguments)
3333
4082
  end
@@ -3341,7 +4090,11 @@ module Prism
3341
4090
 
3342
4091
  # Lazily initialize the parse result.
3343
4092
  def result
3344
- @result ||= Prism.parse(source, partial_script: true, version: "current")
4093
+ @result ||= Prism.parse(source, partial_script: true, version: "current", freeze: true, encoding: source.encoding)
4094
+ end
4095
+
4096
+ def line_and_column_cache
4097
+ @line_and_column_cache ||= LineAndColumnCache.new(result.source)
3345
4098
  end
3346
4099
 
3347
4100
  ##########################################################################
@@ -3362,30 +4115,34 @@ module Prism
3362
4115
  # Visit the string content of a particular node. This method is used to
3363
4116
  # split into the various token types.
3364
4117
  def visit_token(token, allow_keywords = true)
3365
- case token
3366
- when "."
4118
+ if token == "."
3367
4119
  on_period(token)
3368
- when "`"
4120
+ elsif token == "`"
3369
4121
  on_backtick(token)
3370
- when *(allow_keywords ? KEYWORDS : [])
4122
+ elsif allow_keywords && KEYWORDS.include?(token)
3371
4123
  on_kw(token)
3372
- when /^_/
4124
+ elsif token.start_with?("_")
3373
4125
  on_ident(token)
3374
- when /^[[:upper:]]\w*$/
4126
+ elsif token.match?(/^[[:upper:]]\w*$/)
3375
4127
  on_const(token)
3376
- when /^@@/
4128
+ elsif token.start_with?("@@")
3377
4129
  on_cvar(token)
3378
- when /^@/
4130
+ elsif token.start_with?("@")
3379
4131
  on_ivar(token)
3380
- when /^\$/
4132
+ elsif token.start_with?("$")
3381
4133
  on_gvar(token)
3382
- when /^[[:punct:]]/
4134
+ elsif token.match?(/^[[:punct:]]/)
3383
4135
  on_op(token)
3384
4136
  else
3385
4137
  on_ident(token)
3386
4138
  end
3387
4139
  end
3388
4140
 
4141
+ # Visit either `.`, `&.`, or `::`.
4142
+ def visit_call_operator(token)
4143
+ token == "." ? on_period(token) : on_op(token)
4144
+ end
4145
+
3389
4146
  # Visit a node that represents a number. We need to explicitly handle the
3390
4147
  # unary - operator.
3391
4148
  def visit_number_node(node)
@@ -3393,6 +4150,9 @@ module Prism
3393
4150
  location = node.location
3394
4151
 
3395
4152
  if slice[0] == "-"
4153
+ bounds(location.copy(length: 1))
4154
+ on_op("-")
4155
+
3396
4156
  bounds(location.copy(start_offset: location.start_offset + 1))
3397
4157
  value = yield slice[1..-1]
3398
4158
 
@@ -3441,14 +4201,12 @@ module Prism
3441
4201
 
3442
4202
  # This method is responsible for updating lineno and column information
3443
4203
  # to reflect the current node.
3444
- #
3445
- # This method could be drastically improved with some caching on the start
3446
- # of every line, but for now it's good enough.
3447
4204
  def bounds(location)
3448
- @lineno = location.start_line
3449
- @column = location.start_column
4205
+ @lineno, @column = line_and_column_cache.line_and_column(location.start_offset)
3450
4206
  end
3451
4207
 
4208
+ # :startdoc:
4209
+
3452
4210
  ##########################################################################
3453
4211
  # Ripper interface
3454
4212
  ##########################################################################