Class: JsDuck::DocParser
- Inherits:
-
Object
- Object
- JsDuck::DocParser
- 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.
Instance Method Summary collapse
- #add_tag(tag) ⇒ Object
-
#at_alias ⇒ Object
matches @alias <ident-chain>.
-
#at_cfg ⇒ Object
matches @cfg type name …
-
#at_class ⇒ Object
matches @class name …
-
#at_event ⇒ Object
matches @event name …
-
#at_extends ⇒ Object
matches @extends name …
-
#at_inheritdoc ⇒ Object
matches @inheritdoc class.name#static-type-member.
-
#at_member ⇒ Object
matches @member name …
-
#at_method ⇒ Object
matches @method name …
-
#at_param ⇒ Object
matches @param type [name] (optional) …
-
#at_property ⇒ Object
matches @property type name …
-
#at_return ⇒ Object
matches @return type [ return.name ] …
-
#at_throws ⇒ Object
matches @throws type …
-
#at_type ⇒ Object
matches @type type or @type type.
-
#at_var ⇒ Object
matches @var type $name …
-
#at_xtype(tag, namespace) ⇒ Object
matches @xtype/ptype/ftype/…
-
#boolean_at_tag(regex, propname) ⇒ Object
Used to match @private, @ignore, @hide, …
-
#class_list ⇒ Object
matches <ident_chain> <ident_chain> …
-
#class_list_at_tag(regex, tagname) ⇒ Object
matches @<tagname> classname1 classname2 …
-
#default_value ⇒ Object
attempts to match javascript literal, when it fails grabs anything up to closing “]”.
-
#ident ⇒ Object
matches identifier and returns its name.
-
#ident_chain ⇒ Object
matches chained.identifier.name and returns it.
-
#initialize ⇒ DocParser
constructor
A new instance of DocParser.
- #look(re) ⇒ Object
- #match(re) ⇒ Object
-
#maybe_ident_chain(propname) ⇒ Object
matches ident.chain if possible and sets it on @current_tag.
-
#maybe_name ⇒ Object
matches identifier name if possible and sets it on @current_tag.
-
#maybe_name_with_default ⇒ Object
matches: <ident-chain> | “[” <ident-chain> [ “=” <default-value> ] “]”.
-
#maybe_optional ⇒ Object
matches: “(optional)”.
-
#maybe_required ⇒ Object
matches: “(required)”.
-
#maybe_type ⇒ Object
matches type if possible and sets it on @current_tag Also checks for optionality= in type definition.
-
#meta_at_tag(tag) ⇒ Object
Matches the given meta-tag.
- #parse(input) ⇒ Object
- #parse_loop ⇒ Object
-
#purify(input) ⇒ Object
Extracts content inside /** …
-
#skip_horiz_white ⇒ Object
skips horizontal whitespace (tabs and spaces).
- #skip_white ⇒ Object
-
#typedef ⇒ Object
matches …= and returns text inside brackets.
Constructor Details
#initialize ⇒ DocParser
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_alias ⇒ Object
matches @alias <ident-chain>
326 327 328 329 330 331 332 |
# File 'lib/jsduck/doc_parser.rb', line 326 def at_alias match(/@alias/) add_tag(:alias) skip_horiz_white @current_tag[:name] = ident_chain skip_white end |
#at_cfg ⇒ Object
matches @cfg type name …
249 250 251 252 253 254 255 256 |
# File 'lib/jsduck/doc_parser.rb', line 249 def at_cfg match(/@cfg/) add_tag(:cfg) maybe_type maybe_name_with_default maybe_required skip_white end |
#at_class ⇒ Object
matches @class name …
184 185 186 187 188 189 |
# File 'lib/jsduck/doc_parser.rb', line 184 def at_class match(/@class/) add_tag(:class) maybe_ident_chain(:name) skip_white end |
#at_event ⇒ Object
matches @event name …
209 210 211 212 213 214 |
# File 'lib/jsduck/doc_parser.rb', line 209 def at_event match(/@event/) add_tag(:event) maybe_name skip_white end |
#at_extends ⇒ Object
matches @extends name …
192 193 194 195 196 197 |
# File 'lib/jsduck/doc_parser.rb', line 192 def at_extends match(/@extends?/) add_tag(:extends) maybe_ident_chain(:extends) skip_white end |
#at_inheritdoc ⇒ Object
matches @inheritdoc class.name#static-type-member
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/jsduck/doc_parser.rb', line 335 def at_inheritdoc match(/@inherit[dD]oc|@alias/) add_tag(:inheritdoc) skip_horiz_white if look(@ident_chain_pattern) @current_tag[:cls] = ident_chain end 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 skip_white end |
#at_member ⇒ Object
matches @member name …
309 310 311 312 313 314 |
# File 'lib/jsduck/doc_parser.rb', line 309 def at_member match(/@member/) add_tag(:member) maybe_ident_chain(:member) skip_white end |
#at_method ⇒ Object
matches @method name …
217 218 219 220 221 222 |
# File 'lib/jsduck/doc_parser.rb', line 217 def at_method match(/@method/) add_tag(:method) maybe_name skip_white end |
#at_param ⇒ Object
matches @param type [name] (optional) …
225 226 227 228 229 230 231 232 |
# File 'lib/jsduck/doc_parser.rb', line 225 def at_param match(/@param/) add_tag(:param) maybe_type maybe_name_with_default maybe_optional skip_white end |
#at_property ⇒ Object
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.
264 265 266 267 268 269 270 |
# File 'lib/jsduck/doc_parser.rb', line 264 def at_property match(/@property/) add_tag(:property) maybe_type maybe_name_with_default skip_white end |
#at_return ⇒ Object
matches @return type [ return.name ] …
235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/jsduck/doc_parser.rb', line 235 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_throws ⇒ Object
matches @throws type …
282 283 284 285 286 287 |
# File 'lib/jsduck/doc_parser.rb', line 282 def at_throws match(/@throws/) add_tag(:throws) maybe_type skip_white end |
#at_type ⇒ Object
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.
294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/jsduck/doc_parser.rb', line 294 def at_type match(/@type/) add_tag(:type) skip_horiz_white if look(/\{/) tdf = typedef @current_tag[:type] = tdf[:type] @current_tag[:optional] = true if tdf[:optional] elsif look(/\S/) @current_tag[:type] = @input.scan(/\S+/) end skip_white end |
#at_var ⇒ Object
matches @var type $name …
273 274 275 276 277 278 279 |
# File 'lib/jsduck/doc_parser.rb', line 273 def at_var match(/@var/) add_tag(:css_var) maybe_type maybe_name_with_default skip_white end |
#at_xtype(tag, namespace) ⇒ Object
matches @xtype/ptype/ftype/… name
317 318 319 320 321 322 323 |
# File 'lib/jsduck/doc_parser.rb', line 317 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, …
362 363 364 365 366 |
# File 'lib/jsduck/doc_parser.rb', line 362 def boolean_at_tag(regex, propname) match(regex) add_tag(propname) skip_white end |
#class_list ⇒ Object
matches <ident_chain> <ident_chain> … until line end
474 475 476 477 478 479 480 481 482 |
# File 'lib/jsduck/doc_parser.rb', line 474 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 …
200 201 202 203 204 205 206 |
# File 'lib/jsduck/doc_parser.rb', line 200 def class_list_at_tag(regex, tagname) match(regex) add_tag(tagname) skip_horiz_white @current_tag[tagname] = class_list skip_white end |
#default_value ⇒ Object
attempts to match javascript literal, when it fails grabs anything up to closing “]”
435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/jsduck/doc_parser.rb', line 435 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 |
#ident ⇒ Object
matches identifier and returns its name
490 491 492 |
# File 'lib/jsduck/doc_parser.rb', line 490 def ident @input.scan(/\w+/) end |
#ident_chain ⇒ Object
matches chained.identifier.name and returns it
485 486 487 |
# File 'lib/jsduck/doc_parser.rb', line 485 def ident_chain @input.scan(@ident_chain_pattern) end |
#look(re) ⇒ Object
494 495 496 |
# File 'lib/jsduck/doc_parser.rb', line 494 def look(re) @input.check(re) end |
#match(re) ⇒ Object
498 499 500 |
# File 'lib/jsduck/doc_parser.rb', line 498 def match(re) @input.scan(re) end |
#maybe_ident_chain(propname) ⇒ Object
matches ident.chain if possible and sets it on @current_tag
426 427 428 429 430 431 |
# File 'lib/jsduck/doc_parser.rb', line 426 def maybe_ident_chain(propname) skip_horiz_white if look(@ident_chain_pattern) @current_tag[propname] = ident_chain end end |
#maybe_name ⇒ Object
matches identifier name if possible and sets it on @current_tag
418 419 420 421 422 423 |
# File 'lib/jsduck/doc_parser.rb', line 418 def maybe_name skip_horiz_white if look(@ident_pattern) @current_tag[:name] = @input.scan(@ident_pattern) end end |
#maybe_name_with_default ⇒ Object
matches: <ident-chain> | “[” <ident-chain> [ “=” <default-value> ] “]”
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/jsduck/doc_parser.rb', line 380 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_optional ⇒ Object
matches: “(optional)”
400 401 402 403 404 405 406 |
# File 'lib/jsduck/doc_parser.rb', line 400 def maybe_optional skip_horiz_white if look(/\(optional\)/i) match(/\(optional\)/i) @current_tag[:optional] = true end end |
#maybe_required ⇒ Object
matches: “(required)”
409 410 411 412 413 414 415 |
# File 'lib/jsduck/doc_parser.rb', line 409 def maybe_required skip_horiz_white if look(/\(required\)/i) match(/\(required\)/i) @current_tag[:optional] = false end end |
#maybe_type ⇒ Object
matches type if possible and sets it on @current_tag Also checks for optionality= in type definition.
370 371 372 373 374 375 376 377 |
# File 'lib/jsduck/doc_parser.rb', line 370 def maybe_type skip_horiz_white if look(/\{/) tdf = typedef @current_tag[:type] = tdf[:type] @current_tag[:optional] = true if tdf[:optional] end end |
#meta_at_tag(tag) ⇒ Object
Matches the given meta-tag
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/jsduck/doc_parser.rb', line 158 def (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_loop ⇒ Object
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 154 155 |
# 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(/@throws\b/) at_throws 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 (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_white ⇒ Object
skips horizontal whitespace (tabs and spaces)
507 508 509 |
# File 'lib/jsduck/doc_parser.rb', line 507 def skip_horiz_white @input.scan(/[ \t]+/) end |
#skip_white ⇒ Object
502 503 504 |
# File 'lib/jsduck/doc_parser.rb', line 502 def skip_white @input.scan(/\s+/) end |
#typedef ⇒ Object
matches …= and returns text inside brackets
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/jsduck/doc_parser.rb', line 450 def typedef match(/\{/) name = @input.scan(/[^{}]*/) # Type definition can contain nested braces: {{foo:Number}} # In such case we parse the definition so that the braces are balanced. while @input.check(/[{]/) name += "{" + typedef[:type] +"}" name += @input.scan(/[^{}]*/) end if name =~ /=$/ name = name.chop optional = true else optional = nil end match(/\}/) return {:type => name, :optional => optional} end |