Class: JsDuck::DocParser

Inherits:
Object
  • Object
show all
Defined in:
lib/jsduck/doc_parser.rb

Overview

Parses doc-comment into array of @tags

For each @tag it produces Hash like the following:

{
  :tagname => :cfg/:property/:type/:extends/...,
  :doc => "Some documentation for this tag",
  ...@tag specific stuff like :name, :type, and so on...
}

When doc-comment begins with comment, not preceded by @tag, then the comment will be placed into Hash with :tagname => :default.

Unrecognized @tags are left as is into documentation as if they were normal text.

See Also:

  • {@link} are parsed separately in JsDuck::DocFormatter.

Instance Method Summary collapse

Constructor Details

#initializeDocParser

Returns a new instance of DocParser.



27
28
29
30
31
# File 'lib/jsduck/doc_parser.rb', line 27

def initialize
  @ident_pattern = /[$\w-]+/
  @ident_chain_pattern = /[$\w-]+(\.[$\w-]+)*/
  @meta_tags = MetaTagRegistry.instance
end

Instance Method Details

#add_tag(tag) ⇒ Object



80
81
82
# File 'lib/jsduck/doc_parser.rb', line 80

def add_tag(tag)
  @tags << @current_tag = {:tagname => tag, :doc => ""}
end

#at_aliasObject

matches @alias <ident-chain>



314
315
316
317
318
319
320
# File 'lib/jsduck/doc_parser.rb', line 314

def at_alias
  match(/@alias/)
  add_tag(:alias)
  skip_horiz_white
  @current_tag[:name] = ident_chain
  skip_white
end

#at_cfgObject

matches @cfg type name …



247
248
249
250
251
252
253
254
# File 'lib/jsduck/doc_parser.rb', line 247

def at_cfg
  match(/@cfg/)
  add_tag(:cfg)
  maybe_type
  maybe_name_with_default
  maybe_required
  skip_white
end

#at_classObject

matches @class name …



182
183
184
185
186
187
# File 'lib/jsduck/doc_parser.rb', line 182

def at_class
  match(/@class/)
  add_tag(:class)
  maybe_ident_chain(:name)
  skip_white
end

#at_eventObject

matches @event name …



207
208
209
210
211
212
# File 'lib/jsduck/doc_parser.rb', line 207

def at_event
  match(/@event/)
  add_tag(:event)
  maybe_name
  skip_white
end

#at_extendsObject

matches @extends name …



190
191
192
193
194
195
# File 'lib/jsduck/doc_parser.rb', line 190

def at_extends
  match(/@extends?/)
  add_tag(:extends)
  maybe_ident_chain(:extends)
  skip_white
end

#at_inheritdocObject

matches @inheritdoc class.name#static-type-member



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/jsduck/doc_parser.rb', line 323

def at_inheritdoc
  match(/@inherit[dD]oc|@alias/)

  add_tag(:inheritdoc)
  skip_horiz_white
  if look(@ident_chain_pattern)
    @current_tag[:cls] = ident_chain
    if look(/#\w/)
      @input.scan(/#/)
      if look(/static-/)
        @current_tag[:static] = true
        @input.scan(/static-/)
      end
      if look(/(cfg|property|method|event|css_var|css_mixin)-/)
        @current_tag[:type] = ident.to_sym
        @input.scan(/-/)
      end
      @current_tag[:member] = ident
    end
  end
  skip_white
end

#at_memberObject

matches @member name …



297
298
299
300
301
302
# File 'lib/jsduck/doc_parser.rb', line 297

def at_member
  match(/@member/)
  add_tag(:member)
  maybe_ident_chain(:member)
  skip_white
end

#at_methodObject

matches @method name …



215
216
217
218
219
220
# File 'lib/jsduck/doc_parser.rb', line 215

def at_method
  match(/@method/)
  add_tag(:method)
  maybe_name
  skip_white
end

#at_paramObject

matches @param type [name] (optional) …



223
224
225
226
227
228
229
230
# File 'lib/jsduck/doc_parser.rb', line 223

def at_param
  match(/@param/)
  add_tag(:param)
  maybe_type
  maybe_name_with_default
  maybe_optional
  skip_white
end

#at_propertyObject

matches @property type name …

ext-doc doesn’t support type and name for @property - name is inferred from source and @type is required to specify type, jsdoc-toolkit on the other hand follows the sensible route, and so do we.



262
263
264
265
266
267
268
# File 'lib/jsduck/doc_parser.rb', line 262

def at_property
  match(/@property/)
  add_tag(:property)
  maybe_type
  maybe_name_with_default
  skip_white
end

#at_returnObject

matches @return type [ return.name ] …



233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/jsduck/doc_parser.rb', line 233

def at_return
  match(/@returns?/)
  add_tag(:return)
  maybe_type
  skip_white
  if look(/return\.\w/)
    @current_tag[:name] = ident_chain
  else
    @current_tag[:name] = "return"
  end
  skip_white
end

#at_typeObject

matches @type type or @type type

The presence of @type implies that we are dealing with property. ext-doc allows type name to be either inside curly braces or without them at all.



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/jsduck/doc_parser.rb', line 284

def at_type
  match(/@type/)
  add_tag(:type)
  skip_horiz_white
  if look(/\{/)
    @current_tag[:type] = typedef
  elsif look(/\S/)
    @current_tag[:type] = @input.scan(/\S+/)
  end
  skip_white
end

#at_varObject

matches @var type $name …



271
272
273
274
275
276
277
# File 'lib/jsduck/doc_parser.rb', line 271

def at_var
  match(/@var/)
  add_tag(:css_var)
  maybe_type
  maybe_name
  skip_white
end

#at_xtype(tag, namespace) ⇒ Object

matches @xtype/ptype/ftype/… name



305
306
307
308
309
310
311
# File 'lib/jsduck/doc_parser.rb', line 305

def at_xtype(tag, namespace)
  match(tag)
  add_tag(:alias)
  skip_horiz_white
  @current_tag[:name] = namespace + "." + (ident_chain || "")
  skip_white
end

#boolean_at_tag(regex, propname) ⇒ Object

Used to match @private, @ignore, @hide, …



347
348
349
350
351
# File 'lib/jsduck/doc_parser.rb', line 347

def boolean_at_tag(regex, propname)
  match(regex)
  add_tag(propname)
  skip_white
end

#class_listObject

matches <ident_chain> <ident_chain> … until line end



440
441
442
443
444
445
446
447
448
# File 'lib/jsduck/doc_parser.rb', line 440

def class_list
  skip_horiz_white
  classes = []
  while look(@ident_chain_pattern)
    classes << ident_chain
    skip_horiz_white
  end
  classes
end

#class_list_at_tag(regex, tagname) ⇒ Object

matches @<tagname> classname1 classname2 …



198
199
200
201
202
203
204
# File 'lib/jsduck/doc_parser.rb', line 198

def class_list_at_tag(regex, tagname)
  match(regex)
  add_tag(tagname)
  skip_horiz_white
  @current_tag[tagname] = class_list
  skip_white
end

#default_valueObject

attempts to match javascript literal, when it fails grabs anything up to closing “]”



417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/jsduck/doc_parser.rb', line 417

def default_value
  start_pos = @input.pos
  lit = JsLiteralParser.new(@input).literal
  if lit && look(/ *\]/)
    # When lital matched and there's nothing after it up to the closing "]"
    JsLiteralBuilder.new.to_s(lit)
  else
    # Otherwise reset parsing position to where we started
    # and rescan up to "]" using simple regex.
    @input.pos = start_pos
    match(/[^\]]*/)
  end
end

#identObject

matches identifier and returns its name



456
457
458
# File 'lib/jsduck/doc_parser.rb', line 456

def ident
  @input.scan(/\w+/)
end

#ident_chainObject

matches chained.identifier.name and returns it



451
452
453
# File 'lib/jsduck/doc_parser.rb', line 451

def ident_chain
  @input.scan(@ident_chain_pattern)
end

#look(re) ⇒ Object



460
461
462
# File 'lib/jsduck/doc_parser.rb', line 460

def look(re)
  @input.check(re)
end

#match(re) ⇒ Object



464
465
466
# File 'lib/jsduck/doc_parser.rb', line 464

def match(re)
  @input.scan(re)
end

#maybe_ident_chain(propname) ⇒ Object

matches ident.chain if possible and sets it on @current_tag



408
409
410
411
412
413
# File 'lib/jsduck/doc_parser.rb', line 408

def maybe_ident_chain(propname)
  skip_horiz_white
  if look(@ident_chain_pattern)
    @current_tag[propname] = ident_chain
  end
end

#maybe_nameObject

matches identifier name if possible and sets it on @current_tag



400
401
402
403
404
405
# File 'lib/jsduck/doc_parser.rb', line 400

def maybe_name
  skip_horiz_white
  if look(@ident_pattern)
    @current_tag[:name] = @input.scan(@ident_pattern)
  end
end

#maybe_name_with_defaultObject

matches: <ident-chain> | “[” <ident-chain> [ “=” <default-value> ] “]”



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/jsduck/doc_parser.rb', line 362

def maybe_name_with_default
  skip_horiz_white
  if look(/\[/)
    match(/\[/)
    maybe_ident_chain(:name)
    skip_horiz_white
    if look(/=/)
      match(/=/)
      skip_horiz_white
      @current_tag[:default] = default_value
    end
    skip_horiz_white
    match(/\]/)
    @current_tag[:optional] = true
  else
    maybe_ident_chain(:name)
  end
end

#maybe_optionalObject

matches: “(optional)”



382
383
384
385
386
387
388
# File 'lib/jsduck/doc_parser.rb', line 382

def maybe_optional
  skip_horiz_white
  if look(/\(optional\)/i)
    match(/\(optional\)/i)
    @current_tag[:optional] = true
  end
end

#maybe_requiredObject

matches: “(required)”



391
392
393
394
395
396
397
# File 'lib/jsduck/doc_parser.rb', line 391

def maybe_required
  skip_horiz_white
  if look(/\(required\)/i)
    match(/\(required\)/i)
    @current_tag[:optional] = false
  end
end

#maybe_typeObject

matches type if possible and sets it on @current_tag



354
355
356
357
358
359
# File 'lib/jsduck/doc_parser.rb', line 354

def maybe_type
  skip_horiz_white
  if look(/\{/)
    @current_tag[:type] = typedef
  end
end

#meta_at_tag(tag) ⇒ Object

Matches the given meta-tag



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/jsduck/doc_parser.rb', line 156

def meta_at_tag(tag)
  prev_tag = @current_tag

  add_tag(:meta)
  @current_tag[:name] = tag.key
  match(/\w+/)
  skip_horiz_white

  if tag.boolean
    # For boolean tags, only scan the tag name and switch context
    # back to previous tag.
    skip_white
    @current_tag = prev_tag
  elsif tag.multiline
    # For multiline tags we leave the tag open for :doc addition
    # just like with built-in multiline tags.
  else
    # Fors singleline tags, scan to the end of line and finish the
    # tag.
    @current_tag[:doc] = @input.scan(/.*$/).strip
    skip_white
    @current_tag = prev_tag
  end
end

#parse(input) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/jsduck/doc_parser.rb', line 33

def parse(input)
  @tags = []
  @input = StringScanner.new(purify(input))
  parse_loop
  # The parsing process can leave whitespace at the ends of
  # doc-strings, here we get rid of it.  Additionally null all empty docs
  @tags.each do |tag|
    tag[:doc].strip!
    tag[:doc] = nil if tag[:doc] == ""
  end
  # Get rid of empty default tag
  if @tags.first && @tags.first[:tagname] == :default && !@tags.first[:doc]
    @tags.shift
  end
  @tags
end

#parse_loopObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/jsduck/doc_parser.rb', line 84

def parse_loop
  add_tag(:default)
  while !@input.eos? do
    if look(/@class\b/)
      at_class
    elsif look(/@extends?\b/)
      at_extends
    elsif look(/@mixins?\b/)
      class_list_at_tag(/@mixins?/, :mixins)
    elsif look(/@alternateClassNames?\b/)
      class_list_at_tag(/@alternateClassNames?/, :alternateClassNames)
    elsif look(/@uses\b/)
      class_list_at_tag(/@uses/, :uses)
    elsif look(/@requires\b/)
      class_list_at_tag(/@requires/, :requires)
    elsif look(/@singleton\b/)
      boolean_at_tag(/@singleton/, :singleton)
    elsif look(/@event\b/)
      at_event
    elsif look(/@method\b/)
      at_method
    elsif look(/@constructor\b/)
      boolean_at_tag(/@constructor/, :constructor)
    elsif look(/@param\b/)
      at_param
    elsif look(/@returns?\b/)
      at_return
    elsif look(/@cfg\b/)
      at_cfg
    elsif look(/@property\b/)
      at_property
    elsif look(/@type\b/)
      at_type
    elsif look(/@xtype\b/)
      at_xtype(/@xtype/, "widget")
    elsif look(/@ftype\b/)
      at_xtype(/@ftype/, "feature")
    elsif look(/@ptype\b/)
      at_xtype(/@ptype/, "plugin")
    elsif look(/@member\b/)
      at_member
    elsif look(/@inherit[dD]oc\b/)
      at_inheritdoc
    elsif look(/@alias\s+[\w.]+#\w+/)
      # For backwards compatibility.
      # @alias tag was used as @inheritdoc before
      at_inheritdoc
    elsif look(/@alias/)
      at_alias
    elsif look(/@var\b/)
      at_var
    elsif look(/@inheritable\b/)
      boolean_at_tag(/@inheritable/, :inheritable)
    elsif look(/@accessor\b/)
      boolean_at_tag(/@accessor/, :accessor)
    elsif look(/@evented\b/)
      boolean_at_tag(/@evented/, :evented)
    elsif look(/@/)
      @input.scan(/@/)
      tag = @meta_tags[look(/\w+/)]
      if tag
        meta_at_tag(tag)
      else
        @current_tag[:doc] += "@"
      end
    elsif look(/[^@]/)
      @current_tag[:doc] += @input.scan(/[^@]+/)
    end
  end
end

#purify(input) ⇒ Object

Extracts content inside /** … */



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/jsduck/doc_parser.rb', line 51

def purify(input)
  result = []
  # Remove the beginning /** and end */
  input = input.sub(/\A\/\*\* ?/, "").sub(/ ?\*\/\Z/, "")
  # Now we are left with only two types of lines:
  # - those beginning with *
  # - and those without it
  indent = nil
  input.each_line do |line|
    line.chomp!
    if line =~ /\A\s*\*\s?(.*)\Z/
      # When comment contains *-lines, switch indent-trimming off
      indent = 0
      result << $1
    elsif line =~ /\A\s*\Z/
      # pass-through empty lines
      result << line
    elsif indent == nil && line =~ /\A(\s*)(.*?\Z)/
      # When indent not measured, measure it and remember
      indent = $1.length
      result << $2
    else
      # Trim away indent if available
      result << line.sub(/\A\s{0,#{indent||0}}/, "")
    end
  end
  return result.join("\n")
end

#skip_horiz_whiteObject

skips horizontal whitespace (tabs and spaces)



473
474
475
# File 'lib/jsduck/doc_parser.rb', line 473

def skip_horiz_white
  @input.scan(/[ \t]+/)
end

#skip_whiteObject



468
469
470
# File 'lib/jsduck/doc_parser.rb', line 468

def skip_white
  @input.scan(/\s+/)
end

#typedefObject

matches … and returns text inside brackets



432
433
434
435
436
437
# File 'lib/jsduck/doc_parser.rb', line 432

def typedef
  match(/\{/)
  name = @input.scan(/[^}]+/)
  match(/\}/)
  return name
end