sirop 0.1 → 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edde04771185a121f37a4a2df66569803c6f5b18d2a6739923c2a58cedacfc24
4
- data.tar.gz: 364af589b7a6ca122f32447c23dc281b296e6ade1b9f280dca3e6e51b50d456b
3
+ metadata.gz: 5a44761c6907c2bd7358e0b7d6c4079b8582949121ff32161b508a0950d0032c
4
+ data.tar.gz: 99cf621e80d420ec1a4245e8fd9efdbb06633614982d804d45f545f0a1966a0b
5
5
  SHA512:
6
- metadata.gz: 6bb9fc18cefdbe071ba2b1d7f54fd0aa8111f97ffde49c362b4314e92d300e33075711a956384c15ee7880dab8bc85524d63de3ef5625278aa35917fce983066
7
- data.tar.gz: e35e58fc9ba7d223d68298a3a25c8cb30596a1f548e314298de10730b6ea49456486fff541bee1cca5df3f3b092cd59e98fe3e9634a92be75f1d5b4bf5532c6e
6
+ metadata.gz: de992959d865f0ace64d1be7b9c0149a10d193f54e2bfa0e9b20dcd2fe70f7bf4fe3ec5984c29a75cf89bc70dc385d29747ba372174bfe4286654b3543e0f946
7
+ data.tar.gz: '00393520ab9df84615c35a104411d4aad118c07ac4d4212debbf21df536f90883dd924e31e4d9064d4ce7344fa425716c292d4f9fbe3e1fb3b675ccea054fe95'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # 2024-04-19 0.3
2
+
3
+ - Add support for injecting (prefixed) parameters to a lambda node
4
+ - Fix whitespace adjustment in DSL compiler
5
+ - Improve DSL compiler to support embedded string expressions
6
+ - Correctly handle eval'd proc
7
+
8
+ # 2024-02-27 0.2
9
+
10
+ - Update README
11
+ - Remove support for Ruby < 3.2
12
+ - Implement general purpose Finder for finding nodes
13
+ - Implement DSL compiler (for tests)
14
+ - Implement Sirop.to_source
15
+
1
16
  # 2024-02-20 0.1
2
17
 
3
18
  - Find node for a given `Proc` object
data/README.md CHANGED
@@ -1,4 +1,113 @@
1
1
  # Sirop
2
2
 
3
- Sirop is a Ruby code rewriter. More information coming soon...
3
+ Sirop is a Ruby gem for manipulating Ruby source code. Sirop is very young, so
4
+ the following information might be incomplete, out of date, or simply wrong!
4
5
 
6
+ ## Use Cases
7
+
8
+ Some of the use cases addressed by Sirop are:
9
+
10
+ - Compile DSLs into optimized Ruby code. This is especially interesting for HTML
11
+ templating DSLs in libraries like Phlex, Papercraft etc.
12
+ [Example](https://github.com/digital-fabric/sirop/blob/main/test/dsl_compiler.rb)
13
+ - Get the source of a given block or method.
14
+ - Rewrite parts of Ruby code, for implementing Ruby macros (and why not?).
15
+
16
+ ## Limitations
17
+
18
+ - Sirop supports Ruby 3.2 or newer.
19
+ - Sirop can be used only on blocks and methods defined in a file, so cannot
20
+ really be used on dynamically `eval`'d Ruby code, or in an IRB/Pry session.
21
+
22
+ ## Getting the AST/source of a Ruby proc or method
23
+
24
+ To get the AST of a proc or a method, use `Sirop.to_ast`:
25
+
26
+ ```ruby
27
+ # for a proc
28
+ mul = ->(x, y) { x * y }
29
+ Sirop.to_ast(mul) #=> ...
30
+
31
+ # for a method
32
+ def foo; :bar; end
33
+ Sirop.to_ast(method(:foo)) #=> ...
34
+ ```
35
+
36
+ To get the source of a proc or a method, use `Sirop.to_source`:
37
+
38
+ ```ruby
39
+ mul = ->(x, y) { x * y }
40
+ Sirop.to_source(mul) #=> "->(x, y) { x * y }"
41
+
42
+ def foo; :bar; end
43
+ Sirop.to_source(method(:foo)) #=> "def foo; :bar; end"
44
+ ```
45
+
46
+ ## Rewriting Ruby code
47
+
48
+ You can consult the [DSL compiler
49
+ example](https://github.com/digital-fabric/sirop/blob/main/test/dsl_compiler.rb). This example intercepts method calls by defining a `visit_call_node` method:
50
+
51
+ ```ruby
52
+ # Annotated with some explanations
53
+ def visit_call_node(node)
54
+ # don't rewrite if the call has a receiver
55
+ return super if node.receiver
56
+
57
+ # set HTML location start
58
+ @html_location_start ||= node.location
59
+ # get method arguments...
60
+ inner_text, attrs = tag_args(node)
61
+ # and block
62
+ block = node.block
63
+
64
+ # emit HTML tag according to given arguments
65
+ if inner_text
66
+ emit_tag_open(node, attrs)
67
+ emit_tag_inner_text(inner_text)
68
+ emit_tag_close(node)
69
+ elsif block
70
+ emit_tag_open(node, attrs)
71
+ visit(block.body)
72
+ emit_tag_close(node)
73
+ else
74
+ emit_tag_open_close(node, attrs)
75
+ end
76
+ # set HTML location end
77
+ @html_location_end = node.location
78
+ end
79
+ ```
80
+
81
+ ## Future directions
82
+
83
+ - Implement a macro expander with support for `quote`/`unquote`:
84
+
85
+ ```ruby
86
+ trace_macro = Sirop.macro do |ast|
87
+ source = Sirop.to_source(ast)
88
+ quote do
89
+ result = unquote(ast)
90
+ puts "The result of #{source} is: #{result}"
91
+ result
92
+ end
93
+ end
94
+
95
+ def add(x, y)
96
+ trace(x + y)
97
+ end
98
+
99
+ Sirop.expand_macros(method(:add), trace: trace_macro)
100
+ ```
101
+
102
+ - Implement a DSL compiler with hooks for easier usage in DSL libraries.
103
+
104
+ ## Contributing
105
+
106
+ We gladly welcome contributions from anyone! Some areas that need work currently
107
+ are:
108
+
109
+ - Documentation
110
+ - More test cases for Ruby syntax in the Sirop tests. Look here:
111
+ https://github.com/digital-fabric/sirop/tree/main/test/fixtures
112
+
113
+ Please feel free to contribute PR's and issues
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+
5
+ module Sirop
6
+ class Finder < Prism::BasicVisitor
7
+ def self.find(*, &)
8
+ finder = self.new
9
+ finder.find(*, &)
10
+ end
11
+
12
+ def find(root, key, &)
13
+ instance_exec(&)
14
+ @key = key
15
+ catch(key) do
16
+ visit(root)
17
+ nil
18
+ end
19
+ end
20
+
21
+ def found!(node)
22
+ throw(@key, node)
23
+ end
24
+
25
+ def method_missing(sym, node, *args)
26
+ visit_child_nodes(node)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirop
4
+ class Injection
5
+ attr_reader :slice
6
+
7
+ def initialize(slice)
8
+ @slice = slice
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+
5
+ class Prism::BasicVisitor
6
+ def on(type, &)
7
+ singleton_class.define_method(:"visit_#{type}_node", &)
8
+ self
9
+ end
10
+ end
11
+
12
+ class Prism::ParametersNode
13
+ attr_accessor :injected_prefix
14
+ end
15
+
16
+ class Prism::BlockParametersNode
17
+ attr_accessor :injected_parameters
18
+ end
19
+
20
+ class Prism::LambdaNode
21
+ # @param params [String] injected parameters
22
+ # @return [void]
23
+ def inject_parameters(params)
24
+ if parameters
25
+ if parameters.parameters
26
+ parameters.parameters.injected_prefix = Sirop::Injection.new(params)
27
+ else
28
+ parameters.injected_parameters = Sirop::Injection.new(params)
29
+ end
30
+ else
31
+ instance_variable_set(:@parameters, Sirop::Injection.new("(#{params})"))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+
5
+ module Sirop
6
+ #
7
+ class Sourcifier < Prism::BasicVisitor
8
+ attr_reader :buffer
9
+
10
+ def initialize
11
+ @buffer = +''
12
+ end
13
+
14
+ def to_source(node)
15
+ @buffer.clear
16
+ visit(node)
17
+ @buffer
18
+ end
19
+
20
+ def loc_start(loc)
21
+ [loc.start_line, loc.start_column]
22
+ end
23
+
24
+ def loc_end(loc)
25
+ [loc.end_line, loc.end_column]
26
+ end
27
+
28
+ def emit(str)
29
+ @buffer << str
30
+ end
31
+
32
+ def adjust_whitespace(loc)
33
+ return if loc.is_a?(Sirop::Injection)
34
+
35
+ if @last_loc_start
36
+ line_diff = loc.start_line - @last_loc_end.first
37
+ if line_diff > 0
38
+ @buffer << "\n" * line_diff
39
+ @buffer << ' ' * loc.start_column
40
+ elsif line_diff == 0
41
+ ofs = loc.start_column - @last_loc_end.last
42
+ if ofs > 0
43
+ @buffer << ' ' * ofs
44
+ end
45
+ end
46
+ else
47
+ # empty buffer
48
+ @buffer << ' ' * loc.start_column
49
+ end
50
+ @last_loc = loc
51
+ @last_loc_start = loc_start(loc)
52
+ @last_loc_end = loc_end(loc)
53
+ end
54
+
55
+ def emit_code(loc, semicolon: false)
56
+ return if !loc
57
+
58
+ emit_semicolon(loc) if semicolon
59
+ return visit(loc) if loc.is_a?(Prism::Node)
60
+
61
+ adjust_whitespace(loc)
62
+ emit(loc.slice)
63
+ end
64
+
65
+ def emit_verbatim(node)
66
+ emit_code(node.location)
67
+ end
68
+
69
+ def emit_str(str)
70
+ emit(str)
71
+ @last_loc_end[1] += str.size
72
+ end
73
+
74
+ def emit_comma
75
+ emit_str(',')
76
+ end
77
+
78
+ def emit_semicolon(loc)
79
+ loc = loc.location if loc.is_a?(Prism::Node)
80
+ emit_str(';') if loc.start_line == @last_loc.end_line
81
+ end
82
+
83
+ def method_missing(sym, node, *args)
84
+ puts '!' * 40
85
+ p node
86
+ raise NotImplementedError, "Don't know how to handle #{sym}"
87
+ visit_child_nodes(node)
88
+ end
89
+
90
+ VISIT_PLANS = {
91
+ and: [:left, :operator_loc, :right],
92
+ assoc: :visit_child_nodes,
93
+ assoc_splat: [:operator_loc, :value],
94
+ block: [:opening_loc, :parameters, :body, :closing_loc],
95
+ block_argument: [:operator_loc, :expression],
96
+ block_parameter: [:operator_loc, :name_loc],
97
+ block_parameters: [:opening_loc, :injected_parameters, :parameters, :closing_loc],
98
+ break: [:keyword_loc, :arguments],
99
+ constant_path: [:parent, :delimiter_loc, :child],
100
+ constant_read: :emit_verbatim,
101
+ else: [:else_keyword_loc, :statements],
102
+ embedded_statements: [:opening_loc, :statements, :closing_loc],
103
+ false: :emit_verbatim,
104
+ integer: :emit_verbatim,
105
+ keyword_rest_parameter: [:operator_loc, :name_loc],
106
+ keyword_parameter: :emit_verbatim,
107
+ lambda: [:operator_loc, :parameters, :opening_loc, :body,
108
+ :closing_loc],
109
+ local_variable_read: :emit_verbatim,
110
+ local_variable_write: [:name_loc, :operator_loc, :value],
111
+ next: [:keyword_loc, :arguments],
112
+ nil: :emit_verbatim,
113
+ optional_parameter: [:name_loc, :operator_loc, :value],
114
+ optional_keyword_parameter: [:name_loc, :value],
115
+ or: [:left, :operator_loc, :right],
116
+ parentheses: [:opening_loc, :body, :closing_loc],
117
+ required_parameter: :emit_verbatim,
118
+ required_keyword_parameter: :emit_verbatim,
119
+ rest_parameter: [:operator_loc, :name_loc],
120
+ splat: [:operator_loc, :expression],
121
+ statements: :visit_child_nodes,
122
+ string: :emit_verbatim,
123
+ symbol: :emit_verbatim,
124
+ true: :emit_verbatim,
125
+ yield: [:keyword_loc, :lparen_loc, :arguments, :rparen_loc],
126
+ }
127
+
128
+ VISIT_PLANS.each do |key, plan|
129
+ sym = :"visit_#{key}_node"
130
+ define_method(sym) { |n| visit_plan(plan, n) }
131
+ end
132
+
133
+ def visit_plan(plan, node)
134
+ return send(plan, node) if plan.is_a?(Symbol)
135
+
136
+ insert_semicolon = false
137
+ plan.each_with_index do |sym, idx|
138
+ if sym == :semicolon
139
+ insert_semicolon = true
140
+ next
141
+ end
142
+
143
+ obj = node.send(sym)
144
+ emit_code(obj, semicolon: insert_semicolon)
145
+ insert_semicolon = false
146
+ end
147
+ end
148
+
149
+ def visit_comma_separated_nodes(list, comma = false)
150
+ if list
151
+ list.each_with_index do |child, idx|
152
+ emit_comma if comma
153
+ emit_code(child)
154
+ comma = true
155
+ end
156
+ end
157
+ comma
158
+ end
159
+
160
+ def visit_parameters_node(node)
161
+ comma = false
162
+ # injected_prefix is a custom attribute added by Sirop to the
163
+ # ParametersNode class (in lib/sirop/prism_ext.rb). It is used
164
+ # as a way to add a first parameter to a block or method.
165
+ if node.injected_prefix
166
+ emit_code(node.injected_prefix)
167
+ # adjust last_loc_end for proper whitespace after comma
168
+ @last_loc_end[1] -= 2 if @last_loc_end
169
+ # binding.irb
170
+ comma = true
171
+ end
172
+ comma = visit_comma_separated_nodes(node.requireds, comma)
173
+ comma = visit_comma_separated_nodes(node.optionals, comma)
174
+ comma = visit_comma_separated_nodes(node.posts, comma)
175
+ comma = visit_comma_separated_nodes(node.keywords, comma)
176
+ if node.rest
177
+ emit_comma if comma
178
+ comma = true
179
+ emit_code(node.rest)
180
+ end
181
+ if node.keyword_rest
182
+ emit_comma if comma
183
+ comma = true
184
+ emit_code(node.keyword_rest)
185
+ end
186
+ if node.block
187
+ emit_comma if comma
188
+ comma = true
189
+ emit_code(node.block)
190
+ end
191
+ end
192
+
193
+ def visit_arguments_node(node)
194
+ visit_comma_separated_nodes(node.arguments)
195
+ end
196
+
197
+ def visit_keyword_hash_node(node)
198
+ visit_comma_separated_nodes(node.elements)
199
+ end
200
+
201
+ def visit_if_node(node)
202
+ if !node.if_keyword_loc
203
+ return visit_if_node_ternary(node)
204
+ elsif !node.end_keyword_loc
205
+ return visit_if_node_guard(node)
206
+ end
207
+
208
+ emit_code(node.if_keyword_loc)
209
+ emit_code(node.predicate)
210
+ emit_code(node.then_keyword_loc)
211
+ emit_code(node.statements)
212
+ emit_code(node.consequent) if node.consequent
213
+ emit_code(node.end_keyword_loc) if node.if_keyword_loc.slice == 'if'
214
+ end
215
+
216
+ def visit_if_node_ternary(node)
217
+ emit_code(node.predicate)
218
+ emit_code(node.then_keyword_loc)
219
+ emit_code(node.statements)
220
+ emit_code(node.consequent)
221
+ end
222
+
223
+ def visit_if_node_guard(node)
224
+ emit_code(node.statements)
225
+ emit_code(node.if_keyword_loc)
226
+ emit_code(node.predicate)
227
+ end
228
+
229
+ def visit_case_node(node)
230
+ emit_code(node.case_keyword_loc)
231
+ emit_code(node.predicate)
232
+ node.conditions.each { |c| emit_code(c) }
233
+ emit_code(node.consequent)
234
+ emit_code(node.end_keyword_loc)
235
+ end
236
+
237
+ def visit_when_node(node)
238
+ emit_code(node.keyword_loc)
239
+ visit_comma_separated_nodes(node.conditions)
240
+ emit_code(node.statements)
241
+ end
242
+
243
+ def visit_interpolated_symbol_node(node)
244
+ emit_code(node.opening_loc)
245
+ node.parts.each { |p| emit_code(p) }
246
+ emit_code(node.closing_loc)
247
+ end
248
+ alias_method :visit_interpolated_string_node, :visit_interpolated_symbol_node
249
+
250
+ def visit_def_node(node)
251
+ emit_code(node.def_keyword_loc)
252
+ emit_code(node.name_loc)
253
+ last_loc = node.name_loc
254
+
255
+ if node.parameters
256
+ emit_str('(')
257
+ emit_code(node.parameters)
258
+ emit_str(')')
259
+ last_loc = node.parameters.location
260
+ end
261
+
262
+ emit_code(node.body, semicolon: true)
263
+ emit_code(node.end_keyword_loc, semicolon: true)
264
+ end
265
+
266
+ def visit_call_node(node)
267
+ if node.receiver && !node.call_operator_loc && !node.arguments
268
+ return visit_call_node_unary_op(node)
269
+ end
270
+
271
+ block = node.block
272
+
273
+ emit_code(node.receiver)
274
+ emit_code(node.call_operator_loc)
275
+ emit_code(node.message_loc)
276
+ emit_code(node.opening_loc)
277
+ emit_code(node.arguments)
278
+
279
+ if block.is_a?(Prism::BlockArgumentNode)
280
+ emit_comma if node.arguments&.arguments.size > 0
281
+ emit_code(block)
282
+ block = nil
283
+ end
284
+ emit_code(node.closing_loc)
285
+ emit_code(block)
286
+ end
287
+
288
+ def visit_call_node_unary_op(node)
289
+ emit_code(node.message_loc)
290
+ emit_code(node.receiver)
291
+ end
292
+
293
+ def visit_while_node(node)
294
+ return visit_while_node_guard(node) if !node.closing_loc
295
+
296
+ emit_code(node.keyword_loc)
297
+ emit_code(node.predicate)
298
+ emit_code(node.statements, semicolon: true)
299
+ emit_code(node.closing_loc, semicolon: true)
300
+ end
301
+
302
+ def visit_while_node_guard(node)
303
+ emit_code(node.statements)
304
+ emit_code(node.keyword_loc)
305
+ emit_code(node.predicate)
306
+ end
307
+ end
308
+ end
data/lib/sirop/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Sirop
2
2
  # Sirop version
3
- VERSION = '0.1'
3
+ VERSION = '0.3'
4
4
  end
data/lib/sirop.rb CHANGED
@@ -1,27 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'prism'
4
- require 'sirop/block_finder'
4
+ require 'sirop/injection'
5
+ require 'sirop/prism_ext'
6
+ require 'sirop/finder'
7
+ require 'sirop/sourcifier'
5
8
 
6
9
  module Sirop
10
+ class Error < StandardError
11
+ end
12
+
7
13
  class << self
8
- def find(obj)
14
+ def to_ast(obj)
9
15
  case obj
10
16
  when Proc
11
- find_proc(obj)
17
+ proc_ast(obj)
18
+ when UnboundMethod, Method
19
+ method_ast(obj)
12
20
  else
13
21
  raise ArgumentError, "Invalid object type"
14
22
  end
15
23
  end
16
24
 
17
- def find_proc(proc)
25
+ def to_source(obj)
26
+ obj = to_ast(obj) if !obj.is_a?(Prism::Node)
27
+ Sourcifier.new.to_source(obj)
28
+ end
29
+
30
+ private
31
+
32
+ def proc_ast(proc)
18
33
  fn, lineno = proc.source_location
19
34
  pr = Prism.parse(IO.read(fn), filepath: fn)
20
35
  program = pr.value
21
-
22
- finder = Sirop::BlockFinder.new(proc, lineno)
23
- finder.find(program)
36
+
37
+ Finder.find(program, proc) do
38
+ on(:lambda) do |node|
39
+ found!(node) if node.location.start_line == lineno
40
+ super(node)
41
+ end
42
+ on(:call) do |node|
43
+ case node.name
44
+ when :proc, :lambda
45
+ found!(node) if node.block && node.block.location.start_line == lineno
46
+ end
47
+ super(node)
48
+ end
49
+ end
50
+ rescue Errno::ENOENT
51
+ raise Sirop::Error, "Could not get source for proc"
52
+ end
53
+
54
+ def method_ast(method)
55
+ fn, lineno = method.source_location
56
+ pr = Prism.parse(IO.read(fn), filepath: fn)
57
+ program = pr.value
58
+
59
+ Finder.find(program, method) do
60
+ on(:def) do |node|
61
+ found!(node) if node.name == method.name && node.location.start_line == lineno
62
+ super(node)
63
+ end
64
+ end
24
65
  end
25
-
26
66
  end
27
67
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sirop
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-20 00:00:00.000000000 Z
11
+ date: 2024-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prism
@@ -48,15 +48,17 @@ files:
48
48
  - CHANGELOG.md
49
49
  - README.md
50
50
  - lib/sirop.rb
51
- - lib/sirop/block_finder.rb
51
+ - lib/sirop/finder.rb
52
+ - lib/sirop/injection.rb
53
+ - lib/sirop/prism_ext.rb
54
+ - lib/sirop/sourcifier.rb
52
55
  - lib/sirop/version.rb
53
56
  homepage: http://github.com/digital-fabric/sirop
54
57
  licenses:
55
58
  - MIT
56
59
  metadata:
57
- source_code_uri: https://github.com/digital-fabric/sirop
58
- documentation_uri: https://www.rubydoc.info/gems/sirop
59
60
  homepage_uri: https://github.com/digital-fabric/sirop
61
+ documentation_uri: https://www.rubydoc.info/gems/sirop
60
62
  changelog_uri: https://github.com/digital-fabric/sirop/blob/main/CHANGELOG.md
61
63
  post_install_message:
62
64
  rdoc_options:
@@ -70,7 +72,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
72
  requirements:
71
73
  - - ">="
72
74
  - !ruby/object:Gem::Version
73
- version: '3.0'
75
+ version: '3.2'
74
76
  required_rubygems_version: !ruby/object:Gem::Requirement
75
77
  requirements:
76
78
  - - ">="
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sirop
4
- class BlockFinder < Prism::BasicVisitor
5
- attr_accessor :block_node
6
-
7
- def initialize(proc, lineno)
8
- @proc = proc
9
- @lineno = lineno
10
- end
11
-
12
- def find(program)
13
- # p program
14
- # puts
15
- catch(@proc) {
16
- visit(program)
17
- nil
18
- }
19
- end
20
-
21
- def visit_lambda_node(node)
22
- if node.location.start_line == @lineno
23
- throw @proc, node
24
- else
25
- visit_child_nodes(node)
26
- end
27
- end
28
-
29
- def visit_call_node(node)
30
- case node.name
31
- when :proc, :lambda
32
- if node.block && node.block.location.start_line == @lineno
33
- throw @proc, node
34
- end
35
- end
36
- end
37
-
38
- def method_missing(sym, node, *args)
39
- visit_child_nodes(node)
40
- end
41
- end
42
- end