yard2steep 0.1.1 → 0.1.2

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.
@@ -1,66 +1,12 @@
1
- require 'yard2steep/ast'
1
+ require 'ripper'
2
+
2
3
  require 'yard2steep/type'
4
+ require 'yard2steep/ast'
5
+ require 'yard2steep/util'
3
6
 
4
7
  module Yard2steep
5
8
  class Parser
6
- S_RE = /[\s\t]*/
7
- S_P_RE = /[\s\t]+/
8
- PRE_RE = /^#{S_RE}/
9
- POST_RE = /#{S_RE}$/
10
-
11
- CLASS_RE = /
12
- #{PRE_RE}
13
- (class)
14
- #{S_P_RE}
15
- (\w+)
16
- (
17
- #{S_P_RE}
18
- <
19
- #{S_P_RE}
20
- \w+
21
- )?
22
- #{POST_RE}
23
- /x
24
- MODULE_RE = /
25
- #{PRE_RE}
26
- (module)
27
- #{S_P_RE}
28
- (\w+)
29
- #{POST_RE}
30
- /x
31
- S_CLASS_RE = /#{PRE_RE}class#{S_P_RE}<<#{S_P_RE}\w+#{POST_RE}/
32
- END_RE = /#{PRE_RE}end#{POST_RE}/
33
-
34
- CONSTANT_ASSIGN_RE = /
35
- #{PRE_RE}
36
- (
37
- [A-Z\d_]+
38
- )
39
- #{S_RE}
40
- =
41
- .*
42
- #{POST_RE}
43
- /x
44
-
45
- # TODO(south37) `POSTFIX_IF_RE` is wrong. Fix it.
46
- POSTFIX_IF_RE = /
47
- #{PRE_RE}
48
- (?:return|break|next|p|print|raise)
49
- #{S_P_RE}
50
- .*
51
- (?:if|unless)
52
- #{S_P_RE}
53
- .*
54
- $
55
- /x
56
-
57
- BEGIN_END_RE = /
58
- #{S_P_RE}
59
- (if|unless|do|while|until|case|for|begin)
60
- (?:#{S_P_RE}.*)?
61
- $/x
62
-
63
- COMMENT_RE = /#{PRE_RE}#/
9
+ S_RE = /[\s\t]*/
64
10
  TYPE_WITH_PAREN_RE = /
65
11
  \[
66
12
  (
@@ -69,73 +15,30 @@ module Yard2steep
69
15
  )
70
16
  \]
71
17
  /x
72
-
18
+ COMMENT_RE = /^
19
+ \#
20
+ #{S_RE}
21
+ @(?:param|return)
22
+ #{S_RE}
23
+ #{TYPE_WITH_PAREN_RE}
24
+ /x
73
25
  PARAM_RE = /
74
- #{COMMENT_RE}
75
- #{S_P_RE}
26
+ \#
27
+ #{S_RE}
76
28
  @param
77
- #{S_P_RE}
29
+ #{S_RE}
78
30
  #{TYPE_WITH_PAREN_RE}
79
- #{S_P_RE}
31
+ #{S_RE}
80
32
  (\w+)
81
33
  /x
82
34
  RETURN_RE = /
83
- #{COMMENT_RE}
84
- #{S_P_RE}
85
- @return
86
- #{S_P_RE}
87
- #{TYPE_WITH_PAREN_RE}
88
- /x
89
-
90
- PAREN_RE = /
91
- \(
92
- ([^)]*)
93
- \)
94
- /x
95
-
96
- # NOTE: This implementation should be fixed.
97
- ARGS_RE = /
35
+ \#
98
36
  #{S_RE}
99
- \(
100
- [^)]*
101
- \)
102
- |
103
- #{S_P_RE}
104
- .*
105
- /x
106
-
107
- METHOD_RE = /
108
- #{PRE_RE}
109
- def
110
- #{S_P_RE}
111
- (
112
- (?:\w+\.)?
113
- \w+
114
- (?:\!|\?)?
115
- )
37
+ @return
116
38
  #{S_RE}
117
- (
118
- (?:#{ARGS_RE})
119
- ?
120
- )
121
- #{POST_RE}
122
- /x
123
-
124
- # TODO(south37) Support attr_writer, attr_accessor
125
- ATTR_RE = /
126
- #{PRE_RE}
127
- attr_reader
128
- #{S_P_RE}
129
- (:\w+.*)
130
- #{POST_RE}
39
+ #{TYPE_WITH_PAREN_RE}
131
40
  /x
132
41
 
133
- STATES = {
134
- class: "STATES.class",
135
- s_class: "STATES.s_class", # singleton class
136
- method: "STATES.method",
137
- }
138
-
139
42
  ANY_TYPE = 'any'
140
43
  ANY_BLOCK_TYPE = '{ (any) -> any }'
141
44
 
@@ -146,18 +49,7 @@ module Yard2steep
146
49
  # NOTE: set at parse
147
50
  @file = nil
148
51
 
149
- main = AST::ClassNode.create_main
150
- @ast = main
151
-
152
- # Stack of parser state
153
- @stack = [STATES[:class]]
154
- # Parser state. Being last one of STATES in @stack.
155
- @state = STATES[:class]
156
-
157
- # NOTE: reset class context
158
- @current_class = main
159
-
160
- reset_method_context!
52
+ @comments_map = {}
161
53
  end
162
54
 
163
55
  # @param [String] file
@@ -167,171 +59,170 @@ module Yard2steep
167
59
  def parse(file, text, debug: false)
168
60
  @debug = debug
169
61
 
170
- debug_print!("Start parsing: #{file}")
171
-
172
62
  @file = file
173
- text.split(/\n|;/).each do |l|
174
- parse_line(l)
175
- end
63
+ debug_print!("Start parsing: #{file}")
176
64
 
177
- @ast
178
- end
65
+ @comments_map = extract_comments(text)
179
66
 
180
- private
67
+ main = AST::ClassNode.create_main
68
+ @ast = main
181
69
 
182
- # @return [void]
183
- def reset_method_context!
184
- # Current method context. Flushed when method definition is parsed.
185
- @p_types = {}
186
- @r_type = nil
187
- @m_name = nil
188
- end
70
+ # NOTE: reset class context
71
+ @current_class = main
189
72
 
190
- # NOTE: Current implementation override `@p_type`, `@r_type` if it appears
191
- # multiple times before method definition.
192
- #
193
- # @param [String] l
194
- # @return [void]
195
- def parse_line(l)
196
- # At first, try parsing comment
197
- return if try_parse_comment(l)
198
-
199
- return if try_parse_end(l)
200
- return if try_parse_postfix_if(l)
201
- return if try_parse_begin_end(l)
202
-
203
- case @state
204
- when STATES[:class]
205
- return if try_parse_constant(l)
206
- return if try_parse_class(l)
207
- return if try_parse_singleton_class(l)
208
- return if try_parse_method(l)
209
- return if try_parse_attr(l)
210
- when STATES[:s_class]
211
- return if try_parse_method_with_no_action(l)
212
- when STATES[:method]
213
- # Do nothing
214
- else
215
- raise "invalid state: #{@state}"
216
- end
73
+ parse_program(text)
217
74
 
218
- # NOTE: Reach here when other case
75
+ @ast
219
76
  end
220
77
 
221
- # @param [String] l
222
- # @return [bool]
223
- def try_parse_comment(l)
224
- return false unless l.match?(COMMENT_RE)
225
-
226
- try_parse_param_or_return(l)
227
-
228
- true
78
+ # @param [String] text
79
+ # @return [void]
80
+ def parse_program(text)
81
+ r_ast = Ripper.sexp(text)
82
+ # NOTE: r_ast is array such as
83
+ # [:program,
84
+ # [...]]
85
+ Util.assert! { r_ast[0] == :program && r_ast[1].is_a?(Array) }
86
+ parse_stmts(r_ast[1])
229
87
  end
230
88
 
231
- # @param [String] l
232
- # @return [bool]
233
- def try_parse_param_or_return(l)
234
- if @state == STATES[:class]
235
- return if try_parse_param(l)
236
- return if try_parse_return(l)
89
+ # @param [Array] r_ast
90
+ # @return [void]
91
+ def parse_stmts(r_ast)
92
+ r_ast.each do |node|
93
+ parse_stmt(node)
237
94
  end
238
95
  end
239
96
 
240
- # @param [String] l
241
- # @return [bool]
242
- def try_parse_end(l)
243
- return false unless l.match?(END_RE)
244
-
245
- # NOTE: Print before pop state, so offset is -2
246
- debug_print!("end", offset: -2)
247
-
248
- if stack_is_empty?
249
- raise "Invalid end: #{@file}"
97
+ # @param [Array] r_ast
98
+ # @return [void]
99
+ def parse_stmt(r_ast)
100
+ n_type = r_ast[0]
101
+
102
+ case n_type
103
+ when :defs
104
+ parse_defs(r_ast)
105
+ when :def
106
+ parse_def(r_ast)
107
+ when :class, :module
108
+ parse_class_or_module(r_ast)
109
+ when :assign
110
+ parse_assign(r_ast)
111
+ when :command
112
+ parse_command(r_ast)
113
+ when :method_add_arg
114
+ parse_method_add_arg(r_ast)
250
115
  end
251
116
 
252
- pop_state!
253
-
254
- true
117
+ # Do nothing for other stmt
255
118
  end
256
119
 
257
- # NOTE: This implementation is wrong. Used only for skipping postfix if.
258
- #
259
- # @param [String] l
260
- # @return [bool]
261
- def try_parse_postfix_if(l)
262
- l.match?(POSTFIX_IF_RE)
120
+ # @param [Array] r_ast
121
+ # @return [void]
122
+ def parse_defs(r_ast)
123
+ # NOTE: r_ast is array such as
124
+ # [:defs,
125
+ # [:var_ref, [:@kw, "self", [1, 14]]],
126
+ # [:@period, ".", [1, 18]],
127
+ # [:@ident, "my_method", [1, 19]],
128
+ # [:params, nil, nil, nil, nil, nil, nil, nil],
129
+ # [:bodystmt, [[:void_stmt]], nil, nil, nil]]
130
+
131
+ Util.assert! {
132
+ r_ast.size == 6 &&
133
+ r_ast[0] == :defs &&
134
+ r_ast[1][0] == :var_ref &&
135
+ r_ast[1][1][0] == :@kw &&
136
+ r_ast[1][1][1].is_a?(String) &&
137
+ r_ast[1][1][2].is_a?(Array) &&
138
+ r_ast[2][0] == :@period &&
139
+ r_ast[3][0] == :@ident &&
140
+ r_ast[3][1].is_a?(String)
141
+ }
142
+
143
+ m_name = "#{r_ast[1][1][1]}.#{r_ast[3][1]}"
144
+ m_loc = r_ast[1][1][2][0]
145
+ params = r_ast[4]
146
+ # NOTE: We also want to check bodystmt
147
+ # bodystmt = r_ast[5]
148
+
149
+ parse_method_impl(m_name, m_loc, params)
263
150
  end
264
151
 
265
- # @param [String] l
266
- # @return [bool]
267
- def try_parse_begin_end(l)
268
- m = l.match(BEGIN_END_RE)
269
- return false unless m
270
-
271
- debug_print!(m[1])
272
-
273
- push_state!(m[1])
274
-
275
- true
152
+ # @param [Array] r_ast
153
+ # @return [void]
154
+ def parse_def(r_ast)
155
+ # NOTE: r_ast is array such as
156
+ # [:def,
157
+ # [:@ident, "first", [3, 4]],
158
+ # [:paren, [:params, ...]],
159
+ # [:bodystmt, [[:array, nil]], nil, nil, nil]]]
160
+
161
+ Util.assert! {
162
+ r_ast.size == 4 &&
163
+ r_ast[0] == :def &&
164
+ r_ast[1][0] == :@ident &&
165
+ r_ast[1][1].is_a?(String) &&
166
+ r_ast[1][2].is_a?(Array)
167
+ }
168
+
169
+ m_name = r_ast[1][1]
170
+ m_loc = r_ast[1][2][0]
171
+ params = r_ast[2]
172
+ # NOTE: We also want to check bodystmt
173
+ # bodystmt = r_ast[3]
174
+
175
+ parse_method_impl(m_name, m_loc, params)
276
176
  end
277
177
 
278
- # @param [String] l
279
- # @return [bool]
280
- def try_parse_class(l)
281
- m = (l.match(MODULE_RE) || l.match(CLASS_RE))
282
- return false unless m
178
+ # @param [String] m_name
179
+ # @param [Integer] m_loc
180
+ # @param [Array] params
181
+ # @return [void]
182
+ def parse_method_impl(m_name, m_loc, params)
183
+ within_context do
184
+ extract_p_types!(m_loc)
283
185
 
284
- debug_print!("#{m[1]} #{m[2]}")
186
+ p_list = parse_params(params)
285
187
 
286
- # NOTE: If class definition is found before method definition, yard
287
- # annotation is ignored.
288
- reset_method_context!
188
+ # NOTE: We also want to check bodystmt
189
+ # parse_mbody(bodystmt)
289
190
 
290
- c = AST::ClassNode.new(
291
- kind: m[1],
292
- c_name: m[2],
293
- super_c: m[3] && m[3].gsub('<', '').strip,
294
- parent: @current_class,
295
- )
296
- @current_class.append_child(c)
297
- @current_class = c
298
-
299
- push_state!(STATES[:class])
300
-
301
- true
191
+ m_node = AST::MethodNode.new(
192
+ p_list: p_list,
193
+ r_type: (@r_type || ANY_TYPE),
194
+ m_name: m_name,
195
+ )
196
+ @current_class.append_m(m_node)
197
+ end
302
198
  end
303
199
 
304
- # @param [String] l
305
- # @return [bool]
306
- def try_parse_constant(l)
307
- m = l.match(CONSTANT_ASSIGN_RE)
308
- return false unless m
309
-
310
- c = AST::ConstantNode.new(
311
- name: m[1],
312
- klass: @current_class,
313
- )
314
- @current_class.append_constant(c)
315
- true
200
+ # @param [Integer] m_loc represents location of method definition
201
+ # @return [void]
202
+ def extract_p_types!(m_loc)
203
+ Util.assert! { m_loc >= 0 }
204
+ l = m_loc - 1
205
+ while l >= 0
206
+ comment = @comments_map[l]
207
+ break unless comment # nil when no more comment exist
208
+
209
+ parse_comment!(comment)
210
+ l -= 1
211
+ end
316
212
  end
317
213
 
318
- # @param [String] l
319
- # @return [bool]
320
- def try_parse_singleton_class(l)
321
- m = l.match(S_CLASS_RE)
322
- return false unless m
323
-
324
- debug_print!("class <<")
325
-
326
- push_state!(STATES[:s_class])
327
-
328
- true
214
+ # @param [String] comment
215
+ # @return [void]
216
+ def parse_comment!(comment)
217
+ return if try_param_comment(comment)
218
+ return if try_return_comment(comment)
219
+ raise "Must not reach here!"
329
220
  end
330
221
 
331
- # @param [String] l
222
+ # @param [String] comment
332
223
  # @return [bool]
333
- def try_parse_param(l)
334
- m = l.match(PARAM_RE)
224
+ def try_param_comment(comment)
225
+ m = comment.match(PARAM_RE)
335
226
  return false unless m
336
227
 
337
228
  p = AST::PTypeNode.new(
@@ -344,10 +235,10 @@ module Yard2steep
344
235
  true
345
236
  end
346
237
 
347
- # @param [String] l
238
+ # @param [String] comment
348
239
  # @return [bool]
349
- def try_parse_return(l)
350
- m = l.match(RETURN_RE)
240
+ def try_return_comment(comment)
241
+ m = comment.match(RETURN_RE)
351
242
  return false unless m
352
243
 
353
244
  @r_type = normalize_type(m[1])
@@ -355,94 +246,399 @@ module Yard2steep
355
246
  true
356
247
  end
357
248
 
358
- # @param [String] l
359
- # @return [bool]
360
- def try_parse_method(l)
361
- m = l.match(METHOD_RE)
362
- return false unless m
363
-
364
- debug_print!("def #{m[1]}")
365
-
366
- Util.assert! { m[1].is_a?(String) && m[2].is_a?(String) }
367
-
368
- @m_name = m[1]
369
- p_list = parse_method_params(m[2].strip)
370
-
371
- m_node = AST::MethodNode.new(
372
- p_list: p_list,
373
- r_type: (@r_type || ANY_TYPE),
374
- m_name: @m_name,
375
- )
376
- @current_class.append_m(m_node)
377
- reset_method_context!
378
-
379
- push_state!(STATES[:method])
380
-
381
- true
249
+ # @param [Array] r_ast
250
+ # @return [Array<AST::PNode>]
251
+ def parse_params(r_ast)
252
+ # NOTE: parrams is `paren_params` or `no_paren_params`
253
+ # paren_params: [:paren, [:params, ...]]
254
+ # no_paren_params: [:params, ...]
255
+ case r_ast[0]
256
+ when :paren
257
+ parse_paren_params(r_ast)
258
+ when :params
259
+ parse_no_paren_params(r_ast)
260
+ else
261
+ raise "invalid node"
262
+ end
382
263
  end
383
264
 
384
- # @param [String] l
385
- # @return [bool]
386
- def try_parse_method_with_no_action(l)
387
- m = l.match(METHOD_RE)
388
- return false unless m
389
-
390
- debug_print!("def #{m[1]}")
391
-
392
- # Do no action
393
-
394
- push_state!(STATES[:method])
395
-
396
- true
265
+ # @param [Array] r_ast
266
+ # @return [Array<AST::PNode>]
267
+ def parse_paren_params(r_ast)
268
+ # NOTE: r_ast is array such as
269
+ # [:paren, [:params, ...]]
270
+ Util.assert! {
271
+ r_ast[0] == :paren &&
272
+ r_ast[1].is_a?(Array) &&
273
+ r_ast[1][0] == :params
274
+ }
275
+ parse_no_paren_params(r_ast[1])
397
276
  end
398
277
 
399
- # @param [String] params_s
278
+ # @param [Array] params
400
279
  # @return [Array<AST::PNode>]
401
- def parse_method_params(params_s)
402
- Util.assert! { params_s.is_a?(String) }
280
+ def parse_no_paren_params(params)
281
+ # NOTE: params is array such as
282
+ # [:params,
283
+ # nil,
284
+ # nil,
285
+ # nil,
286
+ # nil,
287
+ # [
288
+ # [
289
+ # [:@label, "contents:", [3, 10]],
290
+ # false
291
+ # ]
292
+ # ],
293
+ # nil,
294
+ # nil
295
+ # ]],
296
+ Util.assert! { params[0] == :params && params.size == 8 }
297
+
298
+ r = []
299
+ n_params = (params[1] || [])
300
+ # NOTE: n_params is array such as
301
+ # [[:@ident, "a", [1, 7]], ...]
302
+ n_params.each do |key|
303
+ # TODO(south37) Optimize this check
304
+ Util.assert! { key[0] == :@ident && key[1].is_a?(String) }
305
+ name = key[1]
306
+ r.push(
307
+ AST::PNode.new(
308
+ type_node: type_node(name),
309
+ style: AST::PNode::STYLE[:normal],
310
+ )
311
+ )
312
+ end
403
313
 
404
- # NOTE: Remove parenthesis
405
- if (m = params_s.match(PAREN_RE))
406
- params_s = m[1]
314
+ v_params = (params[2] || [])
315
+ # NOTE: v_params is array such as
316
+ # [
317
+ # [
318
+ # [:@ident, "a", [1, 7]],
319
+ # [:@int, "2", [1, 9]]
320
+ # ],
321
+ # ...
322
+ # ]
323
+ v_params.each do |v_param|
324
+ key, default_v = v_param
325
+
326
+ # TODO(south37) Optimize this check
327
+ Util.assert! {
328
+ key[0] == :@ident &&
329
+ key[1].is_a?(String) &&
330
+ default_v.is_a?(Array)
331
+ }
332
+ name = key[1]
333
+ r.push(
334
+ AST::PNode.new(
335
+ type_node: type_node(name),
336
+ style: AST::PNode::STYLE[:normal_with_default],
337
+ )
338
+ )
407
339
  end
408
340
 
409
- if params_s == ''
410
- if @p_types.size > 0
411
- print "warn: #{@m_name} has no args, but annotated as #{@p_types}"
412
- end
413
- return []
341
+ n_params2 = (params[4] || [])
342
+ # NOTE: n_params2 is array such as
343
+ # [[:@ident, "a", [1, 7]], ...]
344
+ n_params2.each do |key|
345
+ # TODO(south37) Optimize this check
346
+ Util.assert! { key[0] == :@ident && key[1].is_a?(String) }
347
+ name = key[1]
348
+ r.push(
349
+ AST::PNode.new(
350
+ type_node: type_node(name),
351
+ style: AST::PNode::STYLE[:normal],
352
+ )
353
+ )
414
354
  end
415
355
 
416
- params_s.split(',').map { |s| s.strip }.map do |p|
417
- if p.include?(':')
418
- name, default_value = p.split(':')
419
- if default_value
356
+ k_params = (params[5] || [])
357
+ # NOTE: k_params is array such as
358
+ # [
359
+ # [
360
+ # [:@label, "contents:", [3, 10]],
361
+ # false
362
+ # ],
363
+ # ...
364
+ # ],
365
+ k_params.each do |v_param|
366
+ key, default_v = v_param
367
+
368
+ # TODO(south37) Optimize this check
369
+ Util.assert! {
370
+ key[0] == :@label &&
371
+ key[1][-1] == ':'
372
+ }
373
+ name = key[1][0..-2]
374
+ if default_v
375
+ r.push(
420
376
  AST::PNode.new(
421
377
  type_node: type_node(name),
422
378
  style: AST::PNode::STYLE[:keyword_with_default],
423
379
  )
424
- else
380
+ )
381
+ else
382
+ r.push(
425
383
  AST::PNode.new(
426
384
  type_node: type_node(name),
427
385
  style: AST::PNode::STYLE[:keyword],
428
386
  )
429
- end
430
- else
387
+ )
388
+ end
389
+ end
390
+
391
+ b_param = params[7]
392
+ # NOTE: b_params is array such as
393
+ # [:blockarg, [:@ident, "block", [1, 8]]]
394
+ if b_param
395
+ Util.assert! {
396
+ b_param[0] == :blockarg &&
397
+ b_param[1].is_a?(Array) &&
398
+ b_param[1][0] == :@ident &&
399
+ b_param[1][1].is_a?(String)
400
+ }
401
+ name = b_param[1][1]
402
+ r.push(
431
403
  AST::PNode.new(
432
- type_node: type_node(p),
404
+ type_node: block_type_node(name),
433
405
  style: AST::PNode::STYLE[:normal],
434
406
  )
407
+ )
408
+ end
409
+
410
+ # NOTE: params[3] is `*a`, params[6] is `**a`
411
+
412
+ r
413
+ end
414
+
415
+ # @return [void]
416
+ def within_context(&block)
417
+ # Current method context.
418
+ @p_types = {}
419
+ @r_type = nil
420
+ block.call
421
+ end
422
+
423
+ # @param [Array] r_ast
424
+ # @return [void]
425
+ def parse_class_or_module(r_ast)
426
+ # NOTE: r_ast is array such as
427
+ # [:class,
428
+ # [:const_ref, [:@const, "OtherClass", [119, 6]]],
429
+ # [:var_ref, [:@const, "MyClass", [119, 19]]],
430
+ # [:bodystmt, [...]]]
431
+
432
+ kind = r_ast[0].to_s
433
+
434
+ case kind
435
+ when "class"
436
+ Util.assert! { r_ast.size == 4 }
437
+ c_name_node = r_ast[1]
438
+ super_c_node = r_ast[2]
439
+ bodystmt_node = r_ast[3]
440
+ when "module"
441
+ Util.assert! { r_ast.size == 3 }
442
+ c_name_node = r_ast[1]
443
+ super_c_node = nil
444
+ bodystmt_node = r_ast[2]
445
+ else
446
+ raise "invalid kind: #{kind}"
447
+ end
448
+
449
+ Util.assert! {
450
+ c_name_node[0] == :const_ref &&
451
+ c_name_node[1].is_a?(Array) &&
452
+ c_name_node[1][0] == :@const &&
453
+ c_name_node[1][1].is_a?(String)
454
+ }
455
+ c_name = c_name_node[1][1]
456
+ Util.assert! {
457
+ !super_c_node || (
458
+ super_c_node[0] == :var_ref &&
459
+ super_c_node[1].is_a?(Array) &&
460
+ super_c_node[1][0] == :@const &&
461
+ super_c_node[1][1].is_a?(String)
462
+ )
463
+ }
464
+ super_c_name = super_c_node ? super_c_node[1][1] : nil
465
+
466
+ c = AST::ClassNode.new(
467
+ kind: kind,
468
+ c_name: c_name,
469
+ super_c: super_c_name,
470
+ parent: @current_class,
471
+ )
472
+ @current_class.append_child(c)
473
+ @current_class = c
474
+
475
+ parse_bodystmt(bodystmt_node)
476
+
477
+ @current_class = @current_class.parent
478
+ end
479
+
480
+ # @param [Array] r_ast
481
+ # @return [void]
482
+ def parse_bodystmt(r_ast)
483
+ # NOTE:
484
+ # [:bodystmt,
485
+ # [...]]
486
+ Util.assert! {
487
+ r_ast[0] == :bodystmt &&
488
+ r_ast[1].is_a?(Array)
489
+ }
490
+ parse_stmts(r_ast[1])
491
+ end
492
+
493
+ # @param [Array] r_ast
494
+ # @return [void]
495
+ def parse_assign(r_ast)
496
+ # NOTE: r_ast is array such as
497
+ # [:assign,
498
+ # [:var_field, [:@const, "DNA", [1, 15]]],
499
+ # [...]]
500
+ Util.assert! {
501
+ r_ast[0] == :assign &&
502
+ r_ast[1].is_a?(Array) &&
503
+ r_ast[1][0] == :var_field
504
+ }
505
+ var_type = r_ast[1][1][0]
506
+ if var_type == :@const
507
+ # TODO(south37) Check the value node to predict the type of the const.
508
+ c = AST::ConstantNode.new(
509
+ name: r_ast[1][1][1],
510
+ klass: @current_class,
511
+ )
512
+ @current_class.append_constant(c)
513
+ end
514
+ end
515
+
516
+ # @param [Array] r_ast
517
+ # @return [void]
518
+ def parse_command(r_ast)
519
+ # NOTE: r_ast is array such as
520
+ # [:command,
521
+ # [:@ident, "attr_reader", [1, 15]],
522
+ # [:args_add_block,
523
+ # [
524
+ # [:symbol_literal, [:symbol, [:@ident, "ok", [1, 28]]]]
525
+ # ],
526
+ # false
527
+ # ]
528
+ # ]
529
+ Util.assert! {
530
+ r_ast[0] == :command &&
531
+ r_ast[1].is_a?(Array)
532
+ }
533
+
534
+ # NOTE: check attr_*
535
+ if r_ast[1][0] == :@ident
536
+ case r_ast[1][1]
537
+ when "attr_reader"
538
+ parse_attr_reader(parse_command_args_add_block(r_ast[2]))
539
+ when "attr_writer"
540
+ # TODO(south37) Impl
541
+ # parse_attr_writer(parse_command_args_add_block(r_ast[2]))
542
+ when "attr_accessor"
543
+ # TODO(south37) Impl
544
+ # parse_attr_accessor(parse_command_args_add_block(r_ast[2]))
435
545
  end
546
+
547
+ # Do nothing for other case
436
548
  end
437
549
  end
438
550
 
439
- # @param [String] l
440
- # @return [bool]
441
- def try_parse_attr(l)
442
- m = l.match(ATTR_RE)
443
- return false unless m
551
+ # @param [Array] r_ast
552
+ # @return [Array<String>]
553
+ def parse_command_args_add_block(r_ast)
554
+ # NOTE: r_ast is array such as
555
+ #
556
+ # [:args_add_block,
557
+ # [
558
+ # [:symbol_literal, [:symbol, [:@ident, "ok", [1, 28]]]],
559
+ # [:symbol_literal, [:symbol, [:@ident, "no", [1, 33]]]],
560
+ # [:string_literal, [:string_content, [:@tstring_content, "b", [1, 38]]]]
561
+ # ],
562
+ # false
563
+ # ]
564
+ Util.assert! {
565
+ r_ast[0] == :args_add_block &&
566
+ r_ast[1].is_a?(Array)
567
+ }
568
+ r = []
569
+ r_ast[1].each do |ast|
570
+ case ast[0]
571
+ when :symbol_literal
572
+ Util.assert! {
573
+ ast[1].is_a?(Array) &&
574
+ ast[1][0] == :symbol &&
575
+ ast[1][1].is_a?(Array) &&
576
+ ast[1][1][0] == :@ident &&
577
+ ast[1][1][1].is_a?(String)
578
+ }
579
+ r.push(ast[1][1][1])
580
+ when :string_literal
581
+ Util.assert! {
582
+ ast[1].is_a?(Array) &&
583
+ ast[1][0] == :string_content &&
584
+ ast[1][1].is_a?(Array) &&
585
+ ast[1][1][0] == :@tstring_content &&
586
+ ast[1][1][1].is_a?(String)
587
+ }
588
+ r.push(ast[1][1][1])
589
+ end
590
+ # Do nothing for other case
591
+ end
592
+ r
593
+ end
444
594
 
445
- ivars = m[1].split(",").map { |s| s.strip.gsub(/^:/, '') }
595
+ # @param [Array] r_ast
596
+ # @return [void]
597
+ def parse_method_add_arg(r_ast)
598
+ # NOTE: r_ast is Array such as
599
+ # [:method_add_arg,
600
+ # [:fcall, [:@ident, "attr_reader", [1, 15]]],
601
+ # [:arg_paren,
602
+ # [
603
+ # :args_add_block,
604
+ # [
605
+ # [:symbol_literal, [:symbol, [:@ident, "ok", [1, 28]]]]
606
+ # ],
607
+ # false
608
+ # ]
609
+ # ]
610
+ # ]
611
+ Util.assert! {
612
+ r_ast[0] == :method_add_arg &&
613
+ r_ast[1].is_a?(Array) &&
614
+ r_ast[1][0] == :fcall &&
615
+ r_ast[1][1].is_a?(Array) &&
616
+ r_ast[2].is_a?(Array) &&
617
+ r_ast[2][0] == :arg_paren &&
618
+ r_ast[2][1].is_a?(Array) &&
619
+ r_ast[2][1][0] == :args_add_block &&
620
+ r_ast[2][1][1].is_a?(Array)
621
+ }
622
+
623
+ # NOTE: check attr_*
624
+ if r_ast[1][1][0] == :@ident
625
+ case r_ast[1][1][1]
626
+ when "attr_reader"
627
+ parse_attr_reader(parse_command_args_add_block(r_ast[2][1]))
628
+ when "attr_writer"
629
+ # TODO(south37) Impl
630
+ # parse_attr_writer(parse_command_args_add_block(r_ast[2][1]))
631
+ when "attr_accessor"
632
+ # TODO(south37) Impl
633
+ # parse_attr_accessor(parse_command_args_add_block(r_ast[2][1]))
634
+ end
635
+ # Do nothing for other case
636
+ end
637
+ end
638
+
639
+ # @param [Array<String>] vars
640
+ # @return [void]
641
+ def parse_attr_reader(ivars)
446
642
  ivars.each do |ivarname|
447
643
  @current_class.append_ivar(
448
644
  AST::IVarNode.new(
@@ -459,70 +655,67 @@ module Yard2steep
459
655
  )
460
656
  )
461
657
  end
462
-
463
- true
464
- end
465
-
466
- # @param [String] state
467
- # @return [void]
468
- def push_state!(state)
469
- if STATES.values.include?(state)
470
- @state = state
471
- end
472
- @stack.push(state)
473
658
  end
474
659
 
475
- # @return [void]
476
- def pop_state!
477
- state = @stack.pop
478
- if STATES.values.include?(state)
479
- # Restore prev class
480
- if state == STATES[:class]
481
- @current_class = @current_class.parent
482
- end
483
-
484
- # Restore prev state
485
- @state = @stack.select { |s| STATES.values.include?(s) }.last
486
- Util.assert! { !@state.nil? }
660
+ # @param [String] text
661
+ # @return [Hash{ String => String }]
662
+ def extract_comments(text)
663
+ # NOTE: `Ripper.lex` returns array of array such as
664
+ # [
665
+ # [[1, 0], :on_comment, "# @param [Array] contents\n", EXPR_BEG],
666
+ # ...
667
+ # ]
668
+ r = {}
669
+ Ripper.lex(text).each do |t|
670
+ # Check token type
671
+ type = t[1]
672
+ next if type != :on_comment
673
+ # Check comment body
674
+ comment = t[2]
675
+ next unless comment.match?(COMMENT_RE)
676
+
677
+ line = t[0][0]
678
+ r[line] = comment
487
679
  end
488
- end
489
680
 
490
- # @return [bool]
491
- def stack_is_empty?
492
- @stack.size <= 1
681
+ r
493
682
  end
494
683
 
495
684
  ##
496
685
  # Helper
497
686
 
687
+ # @param [String] message
688
+ # @return [void]
689
+ def debug_print!(message)
690
+ print "#{message}\n" if @debug
691
+ end
692
+
498
693
  # @param [String] p
499
694
  # @return [AST::PTypeNode]
500
695
  def type_node(p)
501
696
  if @p_types[p]
502
697
  @p_types[p]
503
698
  else
504
- # NOTE: `&` represents block variable
505
- if p[0] == '&'
506
- AST::PTypeNode.new(
507
- p_type: ANY_BLOCK_TYPE,
508
- p_name: p[1..-1],
509
- kind: AST::PTypeNode::KIND[:block],
510
- )
511
- else
512
- AST::PTypeNode.new(
513
- p_type: ANY_TYPE,
514
- p_name: p,
515
- kind: AST::PTypeNode::KIND[:normal],
516
- )
517
- end
699
+ AST::PTypeNode.new(
700
+ p_type: ANY_TYPE,
701
+ p_name: p,
702
+ kind: AST::PTypeNode::KIND[:normal],
703
+ )
518
704
  end
519
705
  end
520
706
 
521
- # @param [String] message
522
- # @param [Integer] offset
523
- # @return [void]
524
- def debug_print!(message, offset: 0)
525
- print "#{' ' * (@stack.size * 2 + offset)}#{message}\n" if @debug
707
+ # @param [String] p
708
+ # @return [AST::PTypeNode]
709
+ def block_type_node(p)
710
+ if @p_types[p]
711
+ @p_types[p]
712
+ else
713
+ AST::PTypeNode.new(
714
+ p_type: ANY_BLOCK_TYPE,
715
+ p_name: p,
716
+ kind: AST::PTypeNode::KIND[:block],
717
+ )
718
+ end
526
719
  end
527
720
 
528
721
  # @param [String] type