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_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>
316 317 318 319 320 321 322 |
# File 'lib/jsduck/doc_parser.rb', line 316 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 …
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_class ⇒ Object
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_event ⇒ Object
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_extends ⇒ Object
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_inheritdoc ⇒ Object
matches @inheritdoc class.name#static-type-member
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/jsduck/doc_parser.rb', line 325 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 …
299 300 301 302 303 304 |
# File 'lib/jsduck/doc_parser.rb', line 299 def at_member match(/@member/) add_tag(:member) maybe_ident_chain(:member) skip_white end |
#at_method ⇒ Object
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_param ⇒ Object
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_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.
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_return ⇒ Object
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_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.
284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/jsduck/doc_parser.rb', line 284 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 …
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
307 308 309 310 311 312 313 |
# File 'lib/jsduck/doc_parser.rb', line 307 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, …
352 353 354 355 356 |
# File 'lib/jsduck/doc_parser.rb', line 352 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
464 465 466 467 468 469 470 471 472 |
# File 'lib/jsduck/doc_parser.rb', line 464 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_value ⇒ Object
attempts to match javascript literal, when it fails grabs anything up to closing “]”
425 426 427 428 429 430 431 432 433 434 435 436 437 |
# File 'lib/jsduck/doc_parser.rb', line 425 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
480 481 482 |
# File 'lib/jsduck/doc_parser.rb', line 480 def ident @input.scan(/\w+/) end |
#ident_chain ⇒ Object
matches chained.identifier.name and returns it
475 476 477 |
# File 'lib/jsduck/doc_parser.rb', line 475 def ident_chain @input.scan(@ident_chain_pattern) end |
#look(re) ⇒ Object
484 485 486 |
# File 'lib/jsduck/doc_parser.rb', line 484 def look(re) @input.check(re) end |
#match(re) ⇒ Object
488 489 490 |
# File 'lib/jsduck/doc_parser.rb', line 488 def match(re) @input.scan(re) end |
#maybe_ident_chain(propname) ⇒ Object
matches ident.chain if possible and sets it on @current_tag
416 417 418 419 420 421 |
# File 'lib/jsduck/doc_parser.rb', line 416 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
408 409 410 411 412 413 |
# File 'lib/jsduck/doc_parser.rb', line 408 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> ] “]”
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/jsduck/doc_parser.rb', line 370 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)”
390 391 392 393 394 395 396 |
# File 'lib/jsduck/doc_parser.rb', line 390 def maybe_optional skip_horiz_white if look(/\(optional\)/i) match(/\(optional\)/i) @current_tag[:optional] = true end end |
#maybe_required ⇒ Object
matches: “(required)”
399 400 401 402 403 404 405 |
# File 'lib/jsduck/doc_parser.rb', line 399 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.
360 361 362 363 364 365 366 367 |
# File 'lib/jsduck/doc_parser.rb', line 360 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
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 (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 |
# 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 (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)
497 498 499 |
# File 'lib/jsduck/doc_parser.rb', line 497 def skip_horiz_white @input.scan(/[ \t]+/) end |
#skip_white ⇒ Object
492 493 494 |
# File 'lib/jsduck/doc_parser.rb', line 492 def skip_white @input.scan(/\s+/) end |
#typedef ⇒ Object
matches …= and returns text inside brackets
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/jsduck/doc_parser.rb', line 440 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 |