sirop 0.1 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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