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_enum ⇒ Object
matches @enum type 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_override ⇒ Object
matches @override 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 allow balanced braces in default value.
-
#ident ⇒ Object
matches identifier and returns its name.
-
#ident_chain ⇒ Object
matches chained.identifier.name and returns it.
- #indented_as_code? ⇒ Boolean
-
#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.
-
#other_at_tag ⇒ Object
Processes anything else beginning with @-sign.
- #parse(input, filename = "", linenr = 0) ⇒ Object
-
#parse_balanced(re_open, re_close, re_rest) ⇒ Object
Helper method to parse a string up to a closing brace, balancing opening-closing braces in between.
- #parse_loop ⇒ Object
-
#parse_string(quote) ⇒ Object
Parses “…” or ‘…’ including the escape sequence ' or ‘".
-
#parse_with_strings(re_rest) ⇒ Object
Helper for parse_balanced to parse rest of the text between braces, taking account the strings which might occur there.
- #prev_char_is_whitespace? ⇒ Boolean
-
#purify(input) ⇒ Object
Extracts content inside /** …
- #remove_last_tag ⇒ Object
-
#skip_horiz_white ⇒ Object
skips horizontal whitespace (tabs and spaces).
-
#skip_to_next_at_tag ⇒ Object
Skips until the beginning of next @tag.
- #skip_white ⇒ Object
-
#typedef ⇒ Object
matches …= and returns text inside brackets.
Constructor Details
#initialize ⇒ DocParser
Returns a new instance of DocParser.
26 27 28 29 30 |
# File 'lib/jsduck/doc_parser.rb', line 26 def initialize @ident_pattern = /[$\w-]+/ @ident_chain_pattern = /[$\w-]+(\.[$\w-]+)*/ @meta_tags = MetaTagRegistry.instance end |
Instance Method Details
#add_tag(tag) ⇒ Object
79 80 81 |
# File 'lib/jsduck/doc_parser.rb', line 79 def add_tag(tag) @tags << @current_tag = {:tagname => tag, :doc => ""} end |
#at_alias ⇒ Object
matches @alias <ident-chain>
397 398 399 400 401 402 403 |
# File 'lib/jsduck/doc_parser.rb', line 397 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 …
294 295 296 297 298 299 300 301 |
# File 'lib/jsduck/doc_parser.rb', line 294 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 …
229 230 231 232 233 234 |
# File 'lib/jsduck/doc_parser.rb', line 229 def at_class match(/@class/) add_tag(:class) maybe_ident_chain(:name) skip_white end |
#at_enum ⇒ Object
matches @enum type name …
335 336 337 338 339 340 341 342 |
# File 'lib/jsduck/doc_parser.rb', line 335 def at_enum match(/@enum/) add_tag(:class) @current_tag[:enum] = true maybe_type maybe_name_with_default skip_white end |
#at_event ⇒ Object
matches @event name …
254 255 256 257 258 259 |
# File 'lib/jsduck/doc_parser.rb', line 254 def at_event match(/@event/) add_tag(:event) maybe_name skip_white end |
#at_extends ⇒ Object
matches @extends name …
237 238 239 240 241 242 |
# File 'lib/jsduck/doc_parser.rb', line 237 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
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 |
# File 'lib/jsduck/doc_parser.rb', line 406 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/) match(/#/) if look(/static-/) @current_tag[:static] = true match(/static-/) end if look(/(cfg|property|method|event|css_var|css_mixin)-/) @current_tag[:type] = ident.to_sym match(/-/) end @current_tag[:member] = ident end skip_white end |
#at_member ⇒ Object
matches @member name …
380 381 382 383 384 385 |
# File 'lib/jsduck/doc_parser.rb', line 380 def at_member match(/@member/) add_tag(:member) maybe_ident_chain(:member) skip_white end |
#at_method ⇒ Object
matches @method name …
262 263 264 265 266 267 |
# File 'lib/jsduck/doc_parser.rb', line 262 def at_method match(/@method/) add_tag(:method) maybe_name skip_white end |
#at_override ⇒ Object
matches @override name …
345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/jsduck/doc_parser.rb', line 345 def at_override match(/@override/) add_tag(:override) maybe_ident_chain(:class) skip_white # When @override not followed by class name, ignore the tag. # That's because the current ext codebase has some methods # tagged with @override to denote they override something. # But that's not what @override is meant for in JSDuck. unless @current_tag[:class] remove_last_tag end end |
#at_param ⇒ Object
matches @param type [name] (optional) …
270 271 272 273 274 275 276 277 |
# File 'lib/jsduck/doc_parser.rb', line 270 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.
309 310 311 312 313 314 315 |
# File 'lib/jsduck/doc_parser.rb', line 309 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 ] …
280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/jsduck/doc_parser.rb', line 280 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 …
327 328 329 330 331 332 |
# File 'lib/jsduck/doc_parser.rb', line 327 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.
365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/jsduck/doc_parser.rb', line 365 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] = match(/\S+/) end skip_white end |
#at_var ⇒ Object
matches @var type $name …
318 319 320 321 322 323 324 |
# File 'lib/jsduck/doc_parser.rb', line 318 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
388 389 390 391 392 393 394 |
# File 'lib/jsduck/doc_parser.rb', line 388 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, …
433 434 435 436 437 |
# File 'lib/jsduck/doc_parser.rb', line 433 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
573 574 575 576 577 578 579 580 581 |
# File 'lib/jsduck/doc_parser.rb', line 573 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 …
245 246 247 248 249 250 251 |
# File 'lib/jsduck/doc_parser.rb', line 245 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 allow balanced braces in default value. When the nested parsing doesn’t finish at closing “]”, roll back to beginning and simply grab anything up to closing “]”.
507 508 509 510 511 512 513 514 515 516 |
# File 'lib/jsduck/doc_parser.rb', line 507 def default_value start_pos = @input.pos value = parse_balanced(/\[/, /\]/, /[^\[\]'"]*/) if look(/\]/) value else @input.pos = start_pos match(/[^\]]*/) end end |
#ident ⇒ Object
matches identifier and returns its name
589 590 591 |
# File 'lib/jsduck/doc_parser.rb', line 589 def ident @input.scan(/\w+/) end |
#ident_chain ⇒ Object
matches chained.identifier.name and returns it
584 585 586 |
# File 'lib/jsduck/doc_parser.rb', line 584 def ident_chain @input.scan(@ident_chain_pattern) end |
#indented_as_code? ⇒ Boolean
178 179 180 |
# File 'lib/jsduck/doc_parser.rb', line 178 def indented_as_code? @current_tag[:doc] =~ /^ {4,}[^\n]*\z/ end |
#look(re) ⇒ Object
593 594 595 |
# File 'lib/jsduck/doc_parser.rb', line 593 def look(re) @input.check(re) end |
#match(re) ⇒ Object
597 598 599 |
# File 'lib/jsduck/doc_parser.rb', line 597 def match(re) @input.scan(re) end |
#maybe_ident_chain(propname) ⇒ Object
matches ident.chain if possible and sets it on @current_tag
497 498 499 500 501 502 |
# File 'lib/jsduck/doc_parser.rb', line 497 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
489 490 491 492 493 494 |
# File 'lib/jsduck/doc_parser.rb', line 489 def maybe_name skip_horiz_white if look(@ident_pattern) @current_tag[:name] = match(@ident_pattern) end end |
#maybe_name_with_default ⇒ Object
matches: <ident-chain> | “[” <ident-chain> [ “=” <default-value> ] “]”
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/jsduck/doc_parser.rb', line 451 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)”
471 472 473 474 475 476 477 |
# File 'lib/jsduck/doc_parser.rb', line 471 def maybe_optional skip_horiz_white if look(/\(optional\)/i) match(/\(optional\)/i) @current_tag[:optional] = true end end |
#maybe_required ⇒ Object
matches: “(required)”
480 481 482 483 484 485 486 |
# File 'lib/jsduck/doc_parser.rb', line 480 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.
441 442 443 444 445 446 447 448 |
# File 'lib/jsduck/doc_parser.rb', line 441 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
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/jsduck/doc_parser.rb', line 203 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] = match(/.*$/).strip skip_white @current_tag = prev_tag end end |
#other_at_tag ⇒ Object
Processes anything else beginning with @-sign.
-
When @ is not followed by any word chards, do nothing.
-
When it’s one of the meta-tags, process it as such.
-
When it’s something else, print a warning.
188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/jsduck/doc_parser.rb', line 188 def other_at_tag match(/@/) name = look(/\w+/) tag = @meta_tags[name] if tag (tag) elsif name Logger.warn(:tag, "Unsupported tag: @#{name}", @filename, @linenr) @current_tag[:doc] += "@" end end |
#parse(input, filename = "", linenr = 0) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/jsduck/doc_parser.rb', line 32 def parse(input, filename="", linenr=0) @filename = filename @linenr = linenr @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_balanced(re_open, re_close, re_rest) ⇒ Object
Helper method to parse a string up to a closing brace, balancing opening-closing braces in between.
542 543 544 545 546 547 548 549 550 551 |
# File 'lib/jsduck/doc_parser.rb', line 542 def parse_balanced(re_open, re_close, re_rest) result = parse_with_strings(re_rest) while look(re_open) result += match(re_open) result += parse_balanced(re_open, re_close, re_rest) result += match(re_close) result += parse_with_strings(re_rest) end result end |
#parse_loop ⇒ Object
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 156 157 |
# File 'lib/jsduck/doc_parser.rb', line 88 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(/@enum\b/) at_enum elsif look(/@override\b/) at_override 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(/@/) other_at_tag elsif look(/[^@]/) skip_to_next_at_tag end end end |
#parse_string(quote) ⇒ Object
Parses “…” or ‘…’ including the escape sequence ' or ‘"
566 567 568 569 570 |
# File 'lib/jsduck/doc_parser.rb', line 566 def parse_string(quote) re_quote = Regexp.new(quote) re_rest = Regexp.new("(?:[^"+quote+"\\\\]|\\\\.)*") match(re_quote) + match(re_rest) + (match(re_quote) || "") end |
#parse_with_strings(re_rest) ⇒ Object
Helper for parse_balanced to parse rest of the text between braces, taking account the strings which might occur there.
555 556 557 558 559 560 561 562 563 |
# File 'lib/jsduck/doc_parser.rb', line 555 def parse_with_strings(re_rest) result = match(re_rest) while look(/['"]/) result += parse_string('"') if look(/"/) result += parse_string("'") if look(/'/) result += match(re_rest) end result end |
#prev_char_is_whitespace? ⇒ Boolean
174 175 176 |
# File 'lib/jsduck/doc_parser.rb', line 174 def prev_char_is_whitespace? @current_tag[:doc][-1,1] =~ /\s/ end |
#purify(input) ⇒ Object
Extracts content inside /** … */
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 |
# File 'lib/jsduck/doc_parser.rb', line 52 def purify(input) result = [] # We can have 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 |
#remove_last_tag ⇒ Object
83 84 85 86 |
# File 'lib/jsduck/doc_parser.rb', line 83 def remove_last_tag @tags.pop @current_tag = @tags.last end |
#skip_horiz_white ⇒ Object
skips horizontal whitespace (tabs and spaces)
606 607 608 |
# File 'lib/jsduck/doc_parser.rb', line 606 def skip_horiz_white @input.scan(/[ \t]+/) end |
#skip_to_next_at_tag ⇒ Object
Skips until the beginning of next @tag.
There must be space before the next @tag - this ensures that we don’t detect tags inside “[email protected]” or “@link”.
Also check that the @tag is not part of an indented code block - in which case we also ignore the tag.
166 167 168 169 170 171 172 |
# File 'lib/jsduck/doc_parser.rb', line 166 def skip_to_next_at_tag @current_tag[:doc] += match(/[^@]+/) while look(/@/) && (!prev_char_is_whitespace? || indented_as_code?) @current_tag[:doc] += match(/@+[^@]+/) end end |
#skip_white ⇒ Object
601 602 603 |
# File 'lib/jsduck/doc_parser.rb', line 601 def skip_white @input.scan(/\s+/) end |
#typedef ⇒ Object
matches …= and returns text inside brackets
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 |
# File 'lib/jsduck/doc_parser.rb', line 519 def typedef match(/\{/) name = parse_balanced(/\{/, /\}/, /[^{}'"]*/) if name =~ /=$/ name = name.chop optional = true else optional = nil end match(/\}/) return {:type => name, :optional => optional} end |