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
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+ # :markup: markdown
3
+ #--
4
+ # rbs_inline: enabled
5
+
6
+ module Prism
7
+ # Finds the Prism AST node corresponding to a given Method, UnboundMethod,
8
+ # Proc, or Thread::Backtrace::Location. On CRuby, uses node_id from the
9
+ # instruction sequence for an exact match. On other implementations, falls
10
+ # back to best-effort matching by source location line number.
11
+ #
12
+ # This module is autoloaded so that programs that don't use Prism.find don't
13
+ # pay for its definition.
14
+ module NodeFind # :nodoc:
15
+ # Find the node for the given callable or backtrace location.
16
+ #
17
+ #: (Method | UnboundMethod | Proc | Thread::Backtrace::Location callable) -> Node?
18
+ def self.find(callable)
19
+ if callable.respond_to?(:source_range) && !defined?(::RubyVM)
20
+ return SourceRangeFind.new.find(callable)
21
+ end
22
+
23
+ case callable
24
+ when Proc
25
+ if defined?(::RubyVM)
26
+ RubyVMCallableFind.new.find(callable)
27
+ elsif callable.lambda?
28
+ LineLambdaFind.new.find(callable)
29
+ else
30
+ LineProcFind.new.find(callable)
31
+ end
32
+ when Method, UnboundMethod
33
+ if defined?(::RubyVM)
34
+ RubyVMCallableFind.new.find(callable)
35
+ else
36
+ LineMethodFind.new.find(callable)
37
+ end
38
+ when Thread::Backtrace::Location
39
+ if defined?(::RubyVM)
40
+ RubyVMBacktraceLocationFind.new.find(callable)
41
+ else
42
+ LineBacktraceLocationFind.new.find(callable)
43
+ end
44
+ else
45
+ raise ArgumentError, "Expected a Method, UnboundMethod, Proc, or Thread::Backtrace::Location, got #{callable.class}"
46
+ end
47
+ end
48
+
49
+ # Base class that handles parsing a file.
50
+ class Find
51
+ private
52
+
53
+ # Parse the given file path, returning a ParseResult or nil.
54
+ #
55
+ #: (String? file) -> ParseResult?
56
+ def parse_file(file)
57
+ return unless file && File.readable?(file)
58
+ result = Prism.parse_file(file)
59
+ result if result.success?
60
+ end
61
+ end
62
+
63
+ # Finds the AST node for a Method, UnboundMethod, Proc or Thread::Backtrace::Location
64
+ # using the #source_range
65
+ class SourceRangeFind < Find
66
+ # Find the node for the given callable using the #source_range.
67
+ #
68
+ #: (Method | UnboundMethod | Proc | Thread::Backtrace::Location callable) -> Node?
69
+ def find(callable)
70
+ begin
71
+ range = callable.source_range
72
+ rescue ArgumentError # eval
73
+ return
74
+ end
75
+ return unless range
76
+ return unless (result = parse_file(range.absolute_path))
77
+
78
+ start_offset = result.source.byte_offset(range.start_line, range.start_column)
79
+ end_offset = result.source.byte_offset(range.end_line, range.end_column)
80
+ result.value.tunnel(range.start_line, range.start_column).reverse_each do |node|
81
+ if node.start_offset == start_offset && node.end_offset == end_offset
82
+ return node
83
+ end
84
+ end
85
+ nil
86
+ end
87
+ end
88
+
89
+ # Finds the AST node for a Method, UnboundMethod, or Proc using the node_id
90
+ # from the instruction sequence.
91
+ class RubyVMCallableFind < Find
92
+ # Find the node for the given callable using the ISeq node_id.
93
+ #
94
+ #: (Method | UnboundMethod | Proc callable) -> Node?
95
+ def find(callable)
96
+ return unless (source_location = callable.source_location)
97
+ return unless (result = parse_file(source_location[0]))
98
+ return unless (iseq = RubyVM::InstructionSequence.of(callable))
99
+
100
+ header = iseq.to_a[4]
101
+ return unless header[:parser] == :prism
102
+
103
+ result.value.find { |node| node.node_id == header[:node_id] }
104
+ end
105
+ end
106
+
107
+ # Finds the AST node for a Thread::Backtrace::Location using the node_id
108
+ # from the backtrace location.
109
+ class RubyVMBacktraceLocationFind < Find
110
+ # Find the node for the given backtrace location using node_id.
111
+ #
112
+ #: (Thread::Backtrace::Location location) -> Node?
113
+ def find(location)
114
+ file = location.absolute_path || location.path
115
+ return unless (result = parse_file(file))
116
+ return unless RubyVM::AbstractSyntaxTree.respond_to?(:node_id_for_backtrace_location)
117
+
118
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
119
+
120
+ result.value.find { |node| node.node_id == node_id }
121
+ end
122
+ end
123
+
124
+ # Finds the AST node for a Method or UnboundMethod using best-effort line
125
+ # matching. Used on non-CRuby implementations.
126
+ class LineMethodFind < Find
127
+ # Find the node for the given method by matching on name and line.
128
+ #
129
+ #: (Method | UnboundMethod callable) -> Node?
130
+ def find(callable)
131
+ return unless (source_location = callable.source_location)
132
+ return unless (result = parse_file(source_location[0]))
133
+
134
+ name = callable.name
135
+ start_line = source_location[1]
136
+
137
+ result.value.find do |node|
138
+ case node
139
+ when DefNode
140
+ node.name == name && node.location.start_line == start_line
141
+ when CallNode
142
+ node.block.is_a?(BlockNode) && node.location.start_line == start_line
143
+ else
144
+ false
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ # Finds the AST node for a lambda using best-effort line matching. Used
151
+ # on non-CRuby implementations.
152
+ class LineLambdaFind < Find
153
+ # Find the node for the given lambda by matching on line.
154
+ #
155
+ #: (Proc callable) -> Node?
156
+ def find(callable)
157
+ return unless (source_location = callable.source_location)
158
+ return unless (result = parse_file(source_location[0]))
159
+
160
+ start_line = source_location[1]
161
+
162
+ result.value.find do |node|
163
+ case node
164
+ when LambdaNode
165
+ node.location.start_line == start_line
166
+ when CallNode
167
+ node.block.is_a?(BlockNode) && node.location.start_line == start_line
168
+ else
169
+ false
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ # Finds the AST node for a non-lambda Proc using best-effort line
176
+ # matching. Used on non-CRuby implementations.
177
+ class LineProcFind < Find
178
+ # Find the node for the given proc by matching on line.
179
+ #
180
+ #: (Proc callable) -> Node?
181
+ def find(callable)
182
+ return unless (source_location = callable.source_location)
183
+ return unless (result = parse_file(source_location[0]))
184
+
185
+ start_line = source_location[1]
186
+
187
+ result.value.find do |node|
188
+ case node
189
+ when ForNode
190
+ node.location.start_line == start_line
191
+ when CallNode
192
+ node.block.is_a?(BlockNode) && node.location.start_line == start_line
193
+ else
194
+ false
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ # Finds the AST node for a Thread::Backtrace::Location using best-effort
201
+ # line matching. Used on non-CRuby implementations.
202
+ class LineBacktraceLocationFind < Find
203
+ # Find the node for the given backtrace location by matching on line.
204
+ #
205
+ #: (Thread::Backtrace::Location location) -> Node?
206
+ def find(location)
207
+ file = location.absolute_path || location.path
208
+ return unless (result = parse_file(file))
209
+
210
+ start_line = location.lineno
211
+ result.value.find { |node| node.location.start_line == start_line }
212
+ end
213
+ end
214
+ end
215
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # :markup: markdown
3
+ #--
4
+ # rbs_inline: enabled
3
5
 
4
6
  require "stringio"
5
7
 
@@ -9,14 +11,18 @@ module Prism
9
11
  # can be used to format the errors in a human-readable way.
10
12
  class Errors
11
13
  # The parse result that contains the errors.
12
- attr_reader :parse_result
14
+ attr_reader :parse_result #: ParseResult
13
15
 
14
16
  # Initialize a new set of errors from the given parse result.
17
+ #
18
+ #: (ParseResult parse_result) -> void
15
19
  def initialize(parse_result)
16
20
  @parse_result = parse_result
17
21
  end
18
22
 
19
23
  # Formats the errors in a human-readable way and return them as a string.
24
+ #
25
+ #: () -> String
20
26
  def format
21
27
  error_lines = {} #: Hash[Integer, Array[ParseError]]
22
28
  parse_result.errors.each do |error|
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # :markup: markdown
3
+ #--
4
+ # rbs_inline: enabled
3
5
 
4
6
  module Prism
5
7
  class ParseResult < Result
@@ -24,13 +26,20 @@ module Prism
24
26
  # that case. We do that to avoid storing the extra `@newline` instance
25
27
  # variable on every node if we don't need it.
26
28
  class Newlines < Visitor
29
+ # The map of lines indices to whether or not they have been marked as
30
+ # emitting a newline event.
31
+ # @rbs @lines: Array[bool]
32
+
27
33
  # Create a new Newlines visitor with the given newline offsets.
34
+ #
35
+ #: (Integer lines) -> void
28
36
  def initialize(lines)
29
- # @type var lines: Integer
30
37
  @lines = Array.new(1 + lines, false)
31
38
  end
32
39
 
33
- # Permit block/lambda nodes to mark newlines within themselves.
40
+ # Permit block nodes to mark newlines within themselves.
41
+ #
42
+ #: (BlockNode node) -> void
34
43
  def visit_block_node(node)
35
44
  old_lines = @lines
36
45
  @lines = Array.new(old_lines.size, false)
@@ -42,17 +51,150 @@ module Prism
42
51
  end
43
52
  end
44
53
 
45
- alias_method :visit_lambda_node, :visit_block_node
54
+ # Permit lambda nodes to mark newlines within themselves.
55
+ #
56
+ #: (LambdaNode node) -> void
57
+ def visit_lambda_node(node)
58
+ old_lines = @lines
59
+ @lines = Array.new(old_lines.size, false)
60
+
61
+ begin
62
+ super(node)
63
+ ensure
64
+ @lines = old_lines
65
+ end
66
+ end
67
+
68
+ # Permit def nodes to mark newlines within themselves. The body of an
69
+ # endless method definition never emits newline events, so in that case
70
+ # mark every line as already seen while visiting it instead. Nested
71
+ # scopes (blocks, lambdas, etc.) reset the lines and emit events again.
72
+ #
73
+ #: (DefNode node) -> void
74
+ def visit_def_node(node)
75
+ old_lines = @lines
76
+ @lines = Array.new(old_lines.size, !node.equal_loc.nil?)
77
+
78
+ begin
79
+ super(node)
80
+ ensure
81
+ @lines = old_lines
82
+ end
83
+ end
84
+
85
+ # Permit class nodes to mark newlines within themselves.
86
+ #
87
+ #: (ClassNode node) -> void
88
+ def visit_class_node(node)
89
+ old_lines = @lines
90
+ @lines = Array.new(old_lines.size, false)
91
+
92
+ begin
93
+ super(node)
94
+ ensure
95
+ @lines = old_lines
96
+ end
97
+ end
98
+
99
+ # Permit module nodes to mark newlines within themselves.
100
+ #
101
+ #: (ModuleNode node) -> void
102
+ def visit_module_node(node)
103
+ old_lines = @lines
104
+ @lines = Array.new(old_lines.size, false)
105
+
106
+ begin
107
+ super(node)
108
+ ensure
109
+ @lines = old_lines
110
+ end
111
+ end
112
+
113
+ # Permit singleton class nodes to mark newlines within themselves.
114
+ #
115
+ #: (SingletonClassNode node) -> void
116
+ def visit_singleton_class_node(node)
117
+ old_lines = @lines
118
+ @lines = Array.new(old_lines.size, false)
119
+
120
+ begin
121
+ super(node)
122
+ ensure
123
+ @lines = old_lines
124
+ end
125
+ end
126
+
127
+ # Statements inside string interpolation do not emit newline events, so
128
+ # mark every line as already seen while visiting them. Nested scopes
129
+ # (blocks, lambdas, defs, etc.) reset the lines and emit events again.
130
+ #
131
+ #: (EmbeddedStatementsNode node) -> void
132
+ def visit_embedded_statements_node(node)
133
+ old_lines = @lines
134
+ @lines = Array.new(old_lines.size, true)
135
+
136
+ begin
137
+ super(node)
138
+ ensure
139
+ @lines = old_lines
140
+ end
141
+ end
142
+
143
+ # The predicate of a while loop is compiled at the end of the loop,
144
+ # after the body, so any statements it contains (from parentheses)
145
+ # emit their line events again even if the lines were already seen.
146
+ #
147
+ #: (WhileNode node) -> void
148
+ def visit_while_node(node)
149
+ old_lines = @lines
150
+ @lines = Array.new(old_lines.size, false)
151
+
152
+ begin
153
+ visit(node.predicate)
154
+ ensure
155
+ @lines = old_lines
156
+ end
157
+
158
+ visit(node.statements)
159
+ end
160
+
161
+ # The predicate of an until loop is compiled at the end of the loop,
162
+ # after the body, so any statements it contains (from parentheses)
163
+ # emit their line events again even if the lines were already seen.
164
+ #
165
+ #: (UntilNode node) -> void
166
+ def visit_until_node(node)
167
+ old_lines = @lines
168
+ @lines = Array.new(old_lines.size, false)
46
169
 
47
- # Mark if/unless nodes as newlines.
170
+ begin
171
+ visit(node.predicate)
172
+ ensure
173
+ @lines = old_lines
174
+ end
175
+
176
+ visit(node.statements)
177
+ end
178
+
179
+ # Mark if nodes as newlines.
180
+ #
181
+ #: (IfNode node) -> void
48
182
  def visit_if_node(node)
49
183
  node.newline_flag!(@lines)
50
184
  super(node)
51
185
  end
52
186
 
53
- alias_method :visit_unless_node, :visit_if_node
187
+ # Mark unless nodes as newlines.
188
+ #
189
+ #: (UnlessNode node) -> void
190
+ def visit_unless_node(node)
191
+ node.newline_flag!(@lines)
192
+ super(node)
193
+ end
54
194
 
55
195
  # Permit statements lists to mark newlines within themselves.
196
+ #
197
+ #: (StatementsNode node) -> void
56
198
  def visit_statements_node(node)
57
199
  node.body.each do |child|
58
200
  child.newline_flag!(@lines)
@@ -63,10 +205,16 @@ module Prism
63
205
  end
64
206
 
65
207
  class Node
208
+ # Tracks whether or not this node should emit a newline event when the
209
+ # instructions that it represents are executed.
210
+ # @rbs @newline_flag: bool
211
+
212
+ #: () -> bool
66
213
  def newline_flag? # :nodoc:
67
214
  !!defined?(@newline_flag)
68
215
  end
69
216
 
217
+ #: (Array[bool] lines) -> void
70
218
  def newline_flag!(lines) # :nodoc:
71
219
  line = location.start_line
72
220
  unless lines[line]
@@ -77,48 +225,160 @@ module Prism
77
225
  end
78
226
 
79
227
  class BeginNode < Node
228
+ #: (Array[bool] lines) -> void
80
229
  def newline_flag!(lines) # :nodoc:
81
230
  # Never mark BeginNode with a newline flag, mark children instead.
82
231
  end
83
232
  end
84
233
 
85
234
  class ParenthesesNode < Node
235
+ #: (Array[bool] lines) -> void
86
236
  def newline_flag!(lines) # :nodoc:
87
237
  # Never mark ParenthesesNode with a newline flag, mark children instead.
88
238
  end
89
239
  end
90
240
 
91
241
  class IfNode < Node
242
+ #: (Array[bool] lines) -> void
92
243
  def newline_flag!(lines) # :nodoc:
93
244
  predicate.newline_flag!(lines)
94
245
  end
95
246
  end
96
247
 
97
248
  class UnlessNode < Node
249
+ #: (Array[bool] lines) -> void
98
250
  def newline_flag!(lines) # :nodoc:
99
251
  predicate.newline_flag!(lines)
100
252
  end
101
253
  end
102
254
 
103
255
  class UntilNode < Node
256
+ #: (Array[bool] lines) -> void
104
257
  def newline_flag!(lines) # :nodoc:
105
- predicate.newline_flag!(lines)
258
+ if location.start_offset == keyword_loc.start_offset && predicate.is_a?(ParenthesesNode)
259
+ # A parenthesized predicate emits its own line event when it is
260
+ # compiled at the end of the loop, in addition to this one.
261
+ super
262
+ else
263
+ predicate.newline_flag!(lines)
264
+ end
106
265
  end
107
266
  end
108
267
 
109
268
  class WhileNode < Node
269
+ #: (Array[bool] lines) -> void
110
270
  def newline_flag!(lines) # :nodoc:
111
- predicate.newline_flag!(lines)
271
+ if location.start_offset == keyword_loc.start_offset && predicate.is_a?(ParenthesesNode)
272
+ # A parenthesized predicate emits its own line event when it is
273
+ # compiled at the end of the loop, in addition to this one.
274
+ super
275
+ else
276
+ predicate.newline_flag!(lines)
277
+ end
112
278
  end
113
279
  end
114
280
 
115
281
  class RescueModifierNode < Node
282
+ #: (Array[bool] lines) -> void
116
283
  def newline_flag!(lines) # :nodoc:
117
284
  expression.newline_flag!(lines)
118
285
  end
119
286
  end
120
287
 
288
+ # The line event for a statement is emitted where its first instruction is
289
+ # compiled, so nodes whose first instruction comes from a sub-expression
290
+ # delegate their newline flag to that sub-expression: assignments to their
291
+ # value, calls to their receiver, and array, hash, and interpolated string
292
+ # literals to their first element. Static literals are the exception: they
293
+ # are compiled to a single instruction on the first line of the literal, so
294
+ # they do not delegate.
295
+
296
+ class LocalVariableWriteNode < Node
297
+ #: (Array[bool] lines) -> void
298
+ def newline_flag!(lines) # :nodoc:
299
+ value.newline_flag!(lines)
300
+ end
301
+ end
302
+
303
+ class InstanceVariableWriteNode < Node
304
+ #: (Array[bool] lines) -> void
305
+ def newline_flag!(lines) # :nodoc:
306
+ value.newline_flag!(lines)
307
+ end
308
+ end
309
+
310
+ class ClassVariableWriteNode < Node
311
+ #: (Array[bool] lines) -> void
312
+ def newline_flag!(lines) # :nodoc:
313
+ value.newline_flag!(lines)
314
+ end
315
+ end
316
+
317
+ class GlobalVariableWriteNode < Node
318
+ #: (Array[bool] lines) -> void
319
+ def newline_flag!(lines) # :nodoc:
320
+ value.newline_flag!(lines)
321
+ end
322
+ end
323
+
324
+ class ConstantWriteNode < Node
325
+ #: (Array[bool] lines) -> void
326
+ def newline_flag!(lines) # :nodoc:
327
+ value.newline_flag!(lines)
328
+ end
329
+ end
330
+
331
+ class ConstantPathWriteNode < Node
332
+ #: (Array[bool] lines) -> void
333
+ def newline_flag!(lines) # :nodoc:
334
+ value.newline_flag!(lines)
335
+ end
336
+ end
337
+
338
+ class MultiWriteNode < Node
339
+ #: (Array[bool] lines) -> void
340
+ def newline_flag!(lines) # :nodoc:
341
+ value.newline_flag!(lines)
342
+ end
343
+ end
344
+
345
+ class CallNode < Node
346
+ #: (Array[bool] lines) -> void
347
+ def newline_flag!(lines) # :nodoc:
348
+ if (receiver = self.receiver)
349
+ receiver.newline_flag!(lines)
350
+ else
351
+ super
352
+ end
353
+ end
354
+ end
355
+
356
+ class ArrayNode < Node
357
+ #: (Array[bool] lines) -> void
358
+ def newline_flag!(lines) # :nodoc:
359
+ first = elements.first
360
+ if first && !static_literal?
361
+ first.newline_flag!(lines)
362
+ else
363
+ super
364
+ end
365
+ end
366
+ end
367
+
368
+ class HashNode < Node
369
+ #: (Array[bool] lines) -> void
370
+ def newline_flag!(lines) # :nodoc:
371
+ first = elements.first
372
+ if first && !static_literal?
373
+ first.newline_flag!(lines)
374
+ else
375
+ super
376
+ end
377
+ end
378
+ end
379
+
121
380
  class InterpolatedMatchLastLineNode < Node
381
+ #: (Array[bool] lines) -> void
122
382
  def newline_flag!(lines) # :nodoc:
123
383
  first = parts.first
124
384
  first.newline_flag!(lines) if first
@@ -126,6 +386,7 @@ module Prism
126
386
  end
127
387
 
128
388
  class InterpolatedRegularExpressionNode < Node
389
+ #: (Array[bool] lines) -> void
129
390
  def newline_flag!(lines) # :nodoc:
130
391
  first = parts.first
131
392
  first.newline_flag!(lines) if first
@@ -133,13 +394,19 @@ module Prism
133
394
  end
134
395
 
135
396
  class InterpolatedStringNode < Node
397
+ #: (Array[bool] lines) -> void
136
398
  def newline_flag!(lines) # :nodoc:
137
399
  first = parts.first
138
- first.newline_flag!(lines) if first
400
+ if first && !static_literal?
401
+ first.newline_flag!(lines)
402
+ else
403
+ super
404
+ end
139
405
  end
140
406
  end
141
407
 
142
408
  class InterpolatedSymbolNode < Node
409
+ #: (Array[bool] lines) -> void
143
410
  def newline_flag!(lines) # :nodoc:
144
411
  first = parts.first
145
412
  first.newline_flag!(lines) if first
@@ -147,6 +414,7 @@ module Prism
147
414
  end
148
415
 
149
416
  class InterpolatedXStringNode < Node
417
+ #: (Array[bool] lines) -> void
150
418
  def newline_flag!(lines) # :nodoc:
151
419
  first = parts.first
152
420
  first.newline_flag!(lines) if first