You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
899 lines
32 KiB
899 lines
32 KiB
/* |
|
* @fileoverview Main Doctrine object |
|
* @author Yusuke Suzuki <utatane.tea@gmail.com> |
|
* @author Dan Tao <daniel.tao@gmail.com> |
|
* @author Andrew Eisenberg <andrew@eisenberg.as> |
|
*/ |
|
|
|
(function () { |
|
'use strict'; |
|
|
|
var typed, |
|
utility, |
|
jsdoc, |
|
esutils, |
|
hasOwnProperty; |
|
|
|
esutils = require('esutils'); |
|
typed = require('./typed'); |
|
utility = require('./utility'); |
|
|
|
function sliceSource(source, index, last) { |
|
return source.slice(index, last); |
|
} |
|
|
|
hasOwnProperty = (function () { |
|
var func = Object.prototype.hasOwnProperty; |
|
return function hasOwnProperty(obj, name) { |
|
return func.call(obj, name); |
|
}; |
|
}()); |
|
|
|
function shallowCopy(obj) { |
|
var ret = {}, key; |
|
for (key in obj) { |
|
if (obj.hasOwnProperty(key)) { |
|
ret[key] = obj[key]; |
|
} |
|
} |
|
return ret; |
|
} |
|
|
|
function isASCIIAlphanumeric(ch) { |
|
return (ch >= 0x61 /* 'a' */ && ch <= 0x7A /* 'z' */) || |
|
(ch >= 0x41 /* 'A' */ && ch <= 0x5A /* 'Z' */) || |
|
(ch >= 0x30 /* '0' */ && ch <= 0x39 /* '9' */); |
|
} |
|
|
|
function isParamTitle(title) { |
|
return title === 'param' || title === 'argument' || title === 'arg'; |
|
} |
|
|
|
function isReturnTitle(title) { |
|
return title === 'return' || title === 'returns'; |
|
} |
|
|
|
function isProperty(title) { |
|
return title === 'property' || title === 'prop'; |
|
} |
|
|
|
function isNameParameterRequired(title) { |
|
return isParamTitle(title) || isProperty(title) || |
|
title === 'alias' || title === 'this' || title === 'mixes' || title === 'requires'; |
|
} |
|
|
|
function isAllowedName(title) { |
|
return isNameParameterRequired(title) || title === 'const' || title === 'constant'; |
|
} |
|
|
|
function isAllowedNested(title) { |
|
return isProperty(title) || isParamTitle(title); |
|
} |
|
|
|
function isAllowedOptional(title) { |
|
return isProperty(title) || isParamTitle(title); |
|
} |
|
|
|
function isTypeParameterRequired(title) { |
|
return isParamTitle(title) || isReturnTitle(title) || |
|
title === 'define' || title === 'enum' || |
|
title === 'implements' || title === 'this' || |
|
title === 'type' || title === 'typedef' || isProperty(title); |
|
} |
|
|
|
// Consider deprecation instead using 'isTypeParameterRequired' and 'Rules' declaration to pick when a type is optional/required |
|
// This would require changes to 'parseType' |
|
function isAllowedType(title) { |
|
return isTypeParameterRequired(title) || title === 'throws' || title === 'const' || title === 'constant' || |
|
title === 'namespace' || title === 'member' || title === 'var' || title === 'module' || |
|
title === 'constructor' || title === 'class' || title === 'extends' || title === 'augments' || |
|
title === 'public' || title === 'private' || title === 'protected'; |
|
} |
|
|
|
// A regex character class that contains all whitespace except linebreak characters (\r, \n, \u2028, \u2029) |
|
var WHITESPACE = '[ \\f\\t\\v\\u00a0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000\\ufeff]'; |
|
|
|
var STAR_MATCHER = '(' + WHITESPACE + '*(?:\\*' + WHITESPACE + '?)?)(.+|[\r\n\u2028\u2029])'; |
|
|
|
function unwrapComment(doc) { |
|
// JSDoc comment is following form |
|
// /** |
|
// * ....... |
|
// */ |
|
|
|
return doc. |
|
// remove /** |
|
replace(/^\/\*\*?/, ''). |
|
// remove */ |
|
replace(/\*\/$/, ''). |
|
// remove ' * ' at the beginning of a line |
|
replace(new RegExp(STAR_MATCHER, 'g'), '$2'). |
|
// remove trailing whitespace |
|
replace(/\s*$/, ''); |
|
} |
|
|
|
/** |
|
* Converts an index in an "unwrapped" JSDoc comment to the corresponding index in the original "wrapped" version |
|
* @param {string} originalSource The original wrapped comment |
|
* @param {number} unwrappedIndex The index of a character in the unwrapped string |
|
* @returns {number} The index of the corresponding character in the original wrapped string |
|
*/ |
|
function convertUnwrappedCommentIndex(originalSource, unwrappedIndex) { |
|
var replacedSource = originalSource.replace(/^\/\*\*?/, ''); |
|
var numSkippedChars = 0; |
|
var matcher = new RegExp(STAR_MATCHER, 'g'); |
|
var match; |
|
|
|
while ((match = matcher.exec(replacedSource))) { |
|
numSkippedChars += match[1].length; |
|
|
|
if (match.index + match[0].length > unwrappedIndex + numSkippedChars) { |
|
return unwrappedIndex + numSkippedChars + originalSource.length - replacedSource.length; |
|
} |
|
} |
|
|
|
return originalSource.replace(/\*\/$/, '').replace(/\s*$/, '').length; |
|
} |
|
|
|
// JSDoc Tag Parser |
|
|
|
(function (exports) { |
|
var Rules, |
|
index, |
|
lineNumber, |
|
length, |
|
source, |
|
originalSource, |
|
recoverable, |
|
sloppy, |
|
strict; |
|
|
|
function advance() { |
|
var ch = source.charCodeAt(index); |
|
index += 1; |
|
if (esutils.code.isLineTerminator(ch) && !(ch === 0x0D /* '\r' */ && source.charCodeAt(index) === 0x0A /* '\n' */)) { |
|
lineNumber += 1; |
|
} |
|
return String.fromCharCode(ch); |
|
} |
|
|
|
function scanTitle() { |
|
var title = ''; |
|
// waste '@' |
|
advance(); |
|
|
|
while (index < length && isASCIIAlphanumeric(source.charCodeAt(index))) { |
|
title += advance(); |
|
} |
|
|
|
return title; |
|
} |
|
|
|
function seekContent() { |
|
var ch, waiting, last = index; |
|
|
|
waiting = false; |
|
while (last < length) { |
|
ch = source.charCodeAt(last); |
|
if (esutils.code.isLineTerminator(ch) && !(ch === 0x0D /* '\r' */ && source.charCodeAt(last + 1) === 0x0A /* '\n' */)) { |
|
waiting = true; |
|
} else if (waiting) { |
|
if (ch === 0x40 /* '@' */) { |
|
break; |
|
} |
|
if (!esutils.code.isWhiteSpace(ch)) { |
|
waiting = false; |
|
} |
|
} |
|
last += 1; |
|
} |
|
return last; |
|
} |
|
|
|
// type expression may have nest brace, such as, |
|
// { { ok: string } } |
|
// |
|
// therefore, scanning type expression with balancing braces. |
|
function parseType(title, last, addRange) { |
|
var ch, brace, type, startIndex, direct = false; |
|
|
|
|
|
// search '{' |
|
while (index < last) { |
|
ch = source.charCodeAt(index); |
|
if (esutils.code.isWhiteSpace(ch)) { |
|
advance(); |
|
} else if (ch === 0x7B /* '{' */) { |
|
advance(); |
|
break; |
|
} else { |
|
// this is direct pattern |
|
direct = true; |
|
break; |
|
} |
|
} |
|
|
|
|
|
if (direct) { |
|
return null; |
|
} |
|
|
|
// type expression { is found |
|
brace = 1; |
|
type = ''; |
|
while (index < last) { |
|
ch = source.charCodeAt(index); |
|
if (esutils.code.isLineTerminator(ch)) { |
|
advance(); |
|
} else { |
|
if (ch === 0x7D /* '}' */) { |
|
brace -= 1; |
|
if (brace === 0) { |
|
advance(); |
|
break; |
|
} |
|
} else if (ch === 0x7B /* '{' */) { |
|
brace += 1; |
|
} |
|
if (type === '') { |
|
startIndex = index; |
|
} |
|
type += advance(); |
|
} |
|
} |
|
|
|
if (brace !== 0) { |
|
// braces is not balanced |
|
return utility.throwError('Braces are not balanced'); |
|
} |
|
|
|
if (isAllowedOptional(title)) { |
|
return typed.parseParamType(type, {startIndex: convertIndex(startIndex), range: addRange}); |
|
} |
|
|
|
return typed.parseType(type, {startIndex: convertIndex(startIndex), range: addRange}); |
|
} |
|
|
|
function scanIdentifier(last) { |
|
var identifier; |
|
if (!esutils.code.isIdentifierStartES5(source.charCodeAt(index)) && !source[index].match(/[0-9]/)) { |
|
return null; |
|
} |
|
identifier = advance(); |
|
while (index < last && esutils.code.isIdentifierPartES5(source.charCodeAt(index))) { |
|
identifier += advance(); |
|
} |
|
return identifier; |
|
} |
|
|
|
function skipWhiteSpace(last) { |
|
while (index < last && (esutils.code.isWhiteSpace(source.charCodeAt(index)) || esutils.code.isLineTerminator(source.charCodeAt(index)))) { |
|
advance(); |
|
} |
|
} |
|
|
|
function parseName(last, allowBrackets, allowNestedParams) { |
|
var name = '', |
|
useBrackets, |
|
insideString; |
|
|
|
|
|
skipWhiteSpace(last); |
|
|
|
if (index >= last) { |
|
return null; |
|
} |
|
|
|
if (source.charCodeAt(index) === 0x5B /* '[' */) { |
|
if (allowBrackets) { |
|
useBrackets = true; |
|
name = advance(); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
name += scanIdentifier(last); |
|
|
|
if (allowNestedParams) { |
|
if (source.charCodeAt(index) === 0x3A /* ':' */ && ( |
|
name === 'module' || |
|
name === 'external' || |
|
name === 'event')) { |
|
name += advance(); |
|
name += scanIdentifier(last); |
|
|
|
} |
|
if(source.charCodeAt(index) === 0x5B /* '[' */ && source.charCodeAt(index + 1) === 0x5D /* ']' */){ |
|
name += advance(); |
|
name += advance(); |
|
} |
|
while (source.charCodeAt(index) === 0x2E /* '.' */ || |
|
source.charCodeAt(index) === 0x2F /* '/' */ || |
|
source.charCodeAt(index) === 0x23 /* '#' */ || |
|
source.charCodeAt(index) === 0x2D /* '-' */ || |
|
source.charCodeAt(index) === 0x7E /* '~' */) { |
|
name += advance(); |
|
name += scanIdentifier(last); |
|
} |
|
} |
|
|
|
if (useBrackets) { |
|
skipWhiteSpace(last); |
|
// do we have a default value for this? |
|
if (source.charCodeAt(index) === 0x3D /* '=' */) { |
|
// consume the '='' symbol |
|
name += advance(); |
|
skipWhiteSpace(last); |
|
|
|
var ch; |
|
var bracketDepth = 1; |
|
|
|
// scan in the default value |
|
while (index < last) { |
|
ch = source.charCodeAt(index); |
|
|
|
if (esutils.code.isWhiteSpace(ch)) { |
|
if (!insideString) { |
|
skipWhiteSpace(last); |
|
ch = source.charCodeAt(index); |
|
} |
|
} |
|
|
|
if (ch === 0x27 /* ''' */) { |
|
if (!insideString) { |
|
insideString = '\''; |
|
} else { |
|
if (insideString === '\'') { |
|
insideString = ''; |
|
} |
|
} |
|
} |
|
|
|
if (ch === 0x22 /* '"' */) { |
|
if (!insideString) { |
|
insideString = '"'; |
|
} else { |
|
if (insideString === '"') { |
|
insideString = ''; |
|
} |
|
} |
|
} |
|
|
|
if (ch === 0x5B /* '[' */) { |
|
bracketDepth++; |
|
} else if (ch === 0x5D /* ']' */ && |
|
--bracketDepth === 0) { |
|
break; |
|
} |
|
|
|
name += advance(); |
|
} |
|
} |
|
|
|
skipWhiteSpace(last); |
|
|
|
if (index >= last || source.charCodeAt(index) !== 0x5D /* ']' */) { |
|
// we never found a closing ']' |
|
return null; |
|
} |
|
|
|
// collect the last ']' |
|
name += advance(); |
|
} |
|
|
|
return name; |
|
} |
|
|
|
function skipToTag() { |
|
while (index < length && source.charCodeAt(index) !== 0x40 /* '@' */) { |
|
advance(); |
|
} |
|
if (index >= length) { |
|
return false; |
|
} |
|
utility.assert(source.charCodeAt(index) === 0x40 /* '@' */); |
|
return true; |
|
} |
|
|
|
function convertIndex(rangeIndex) { |
|
if (source === originalSource) { |
|
return rangeIndex; |
|
} |
|
return convertUnwrappedCommentIndex(originalSource, rangeIndex); |
|
} |
|
|
|
function TagParser(options, title) { |
|
this._options = options; |
|
this._title = title.toLowerCase(); |
|
this._tag = { |
|
title: title, |
|
description: null |
|
}; |
|
if (this._options.lineNumbers) { |
|
this._tag.lineNumber = lineNumber; |
|
} |
|
this._first = index - title.length - 1; |
|
this._last = 0; |
|
// space to save special information for title parsers. |
|
this._extra = { }; |
|
} |
|
|
|
// addError(err, ...) |
|
TagParser.prototype.addError = function addError(errorText) { |
|
var args = Array.prototype.slice.call(arguments, 1), |
|
msg = errorText.replace( |
|
/%(\d)/g, |
|
function (whole, index) { |
|
utility.assert(index < args.length, 'Message reference must be in range'); |
|
return args[index]; |
|
} |
|
); |
|
|
|
if (!this._tag.errors) { |
|
this._tag.errors = []; |
|
} |
|
if (strict) { |
|
utility.throwError(msg); |
|
} |
|
this._tag.errors.push(msg); |
|
return recoverable; |
|
}; |
|
|
|
TagParser.prototype.parseType = function () { |
|
// type required titles |
|
if (isTypeParameterRequired(this._title)) { |
|
try { |
|
this._tag.type = parseType(this._title, this._last, this._options.range); |
|
if (!this._tag.type) { |
|
if (!isParamTitle(this._title) && !isReturnTitle(this._title)) { |
|
if (!this.addError('Missing or invalid tag type')) { |
|
return false; |
|
} |
|
} |
|
} |
|
} catch (error) { |
|
this._tag.type = null; |
|
if (!this.addError(error.message)) { |
|
return false; |
|
} |
|
} |
|
} else if (isAllowedType(this._title)) { |
|
// optional types |
|
try { |
|
this._tag.type = parseType(this._title, this._last, this._options.range); |
|
} catch (e) { |
|
//For optional types, lets drop the thrown error when we hit the end of the file |
|
} |
|
} |
|
return true; |
|
}; |
|
|
|
TagParser.prototype._parseNamePath = function (optional) { |
|
var name; |
|
name = parseName(this._last, sloppy && isAllowedOptional(this._title), true); |
|
if (!name) { |
|
if (!optional) { |
|
if (!this.addError('Missing or invalid tag name')) { |
|
return false; |
|
} |
|
} |
|
} |
|
this._tag.name = name; |
|
return true; |
|
}; |
|
|
|
TagParser.prototype.parseNamePath = function () { |
|
return this._parseNamePath(false); |
|
}; |
|
|
|
TagParser.prototype.parseNamePathOptional = function () { |
|
return this._parseNamePath(true); |
|
}; |
|
|
|
|
|
TagParser.prototype.parseName = function () { |
|
var assign, name; |
|
|
|
// param, property requires name |
|
if (isAllowedName(this._title)) { |
|
this._tag.name = parseName(this._last, sloppy && isAllowedOptional(this._title), isAllowedNested(this._title)); |
|
if (!this._tag.name) { |
|
if (!isNameParameterRequired(this._title)) { |
|
return true; |
|
} |
|
|
|
// it's possible the name has already been parsed but interpreted as a type |
|
// it's also possible this is a sloppy declaration, in which case it will be |
|
// fixed at the end |
|
if (isParamTitle(this._title) && this._tag.type && this._tag.type.name) { |
|
this._extra.name = this._tag.type; |
|
this._tag.name = this._tag.type.name; |
|
this._tag.type = null; |
|
} else { |
|
if (!this.addError('Missing or invalid tag name')) { |
|
return false; |
|
} |
|
} |
|
} else { |
|
name = this._tag.name; |
|
if (name.charAt(0) === '[' && name.charAt(name.length - 1) === ']') { |
|
// extract the default value if there is one |
|
// example: @param {string} [somebody=John Doe] description |
|
assign = name.substring(1, name.length - 1).split('='); |
|
if (assign.length > 1) { |
|
this._tag['default'] = assign.slice(1).join('='); |
|
} |
|
this._tag.name = assign[0]; |
|
|
|
// convert to an optional type |
|
if (this._tag.type && this._tag.type.type !== 'OptionalType') { |
|
this._tag.type = { |
|
type: 'OptionalType', |
|
expression: this._tag.type |
|
}; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
return true; |
|
}; |
|
|
|
TagParser.prototype.parseDescription = function parseDescription() { |
|
var description = sliceSource(source, index, this._last).trim(); |
|
if (description) { |
|
if ((/^-\s+/).test(description)) { |
|
description = description.substring(2); |
|
} |
|
this._tag.description = description; |
|
} |
|
return true; |
|
}; |
|
|
|
TagParser.prototype.parseCaption = function parseDescription() { |
|
var description = sliceSource(source, index, this._last).trim(); |
|
var captionStartTag = '<caption>'; |
|
var captionEndTag = '</caption>'; |
|
var captionStart = description.indexOf(captionStartTag); |
|
var captionEnd = description.indexOf(captionEndTag); |
|
if (captionStart >= 0 && captionEnd >= 0) { |
|
this._tag.caption = description.substring( |
|
captionStart + captionStartTag.length, captionEnd).trim(); |
|
this._tag.description = description.substring(captionEnd + captionEndTag.length).trim(); |
|
} else { |
|
this._tag.description = description; |
|
} |
|
return true; |
|
}; |
|
|
|
TagParser.prototype.parseKind = function parseKind() { |
|
var kind, kinds; |
|
kinds = { |
|
'class': true, |
|
'constant': true, |
|
'event': true, |
|
'external': true, |
|
'file': true, |
|
'function': true, |
|
'member': true, |
|
'mixin': true, |
|
'module': true, |
|
'namespace': true, |
|
'typedef': true |
|
}; |
|
kind = sliceSource(source, index, this._last).trim(); |
|
this._tag.kind = kind; |
|
if (!hasOwnProperty(kinds, kind)) { |
|
if (!this.addError('Invalid kind name \'%0\'', kind)) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
}; |
|
|
|
TagParser.prototype.parseAccess = function parseAccess() { |
|
var access; |
|
access = sliceSource(source, index, this._last).trim(); |
|
this._tag.access = access; |
|
if (access !== 'private' && access !== 'protected' && access !== 'public') { |
|
if (!this.addError('Invalid access name \'%0\'', access)) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
}; |
|
|
|
TagParser.prototype.parseThis = function parseThis() { |
|
// this name may be a name expression (e.g. {foo.bar}), |
|
// an union (e.g. {foo.bar|foo.baz}) or a name path (e.g. foo.bar) |
|
var value = sliceSource(source, index, this._last).trim(); |
|
if (value && value.charAt(0) === '{') { |
|
var gotType = this.parseType(); |
|
if (gotType && this._tag.type.type === 'NameExpression' || this._tag.type.type === 'UnionType') { |
|
this._tag.name = this._tag.type.name; |
|
return true; |
|
} else { |
|
return this.addError('Invalid name for this'); |
|
} |
|
} else { |
|
return this.parseNamePath(); |
|
} |
|
}; |
|
|
|
TagParser.prototype.parseVariation = function parseVariation() { |
|
var variation, text; |
|
text = sliceSource(source, index, this._last).trim(); |
|
variation = parseFloat(text, 10); |
|
this._tag.variation = variation; |
|
if (isNaN(variation)) { |
|
if (!this.addError('Invalid variation \'%0\'', text)) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
}; |
|
|
|
TagParser.prototype.ensureEnd = function () { |
|
var shouldBeEmpty = sliceSource(source, index, this._last).trim(); |
|
if (shouldBeEmpty) { |
|
if (!this.addError('Unknown content \'%0\'', shouldBeEmpty)) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
}; |
|
|
|
TagParser.prototype.epilogue = function epilogue() { |
|
var description; |
|
|
|
description = this._tag.description; |
|
// un-fix potentially sloppy declaration |
|
if (isAllowedOptional(this._title) && !this._tag.type && description && description.charAt(0) === '[') { |
|
this._tag.type = this._extra.name; |
|
if (!this._tag.name) { |
|
this._tag.name = undefined; |
|
} |
|
|
|
if (!sloppy) { |
|
if (!this.addError('Missing or invalid tag name')) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
}; |
|
|
|
Rules = { |
|
// http://usejsdoc.org/tags-access.html |
|
'access': ['parseAccess'], |
|
// http://usejsdoc.org/tags-alias.html |
|
'alias': ['parseNamePath', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-augments.html |
|
'augments': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-constructor.html |
|
'constructor': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// Synonym: http://usejsdoc.org/tags-constructor.html |
|
'class': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// Synonym: http://usejsdoc.org/tags-extends.html |
|
'extends': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-example.html |
|
'example': ['parseCaption'], |
|
// http://usejsdoc.org/tags-deprecated.html |
|
'deprecated': ['parseDescription'], |
|
// http://usejsdoc.org/tags-global.html |
|
'global': ['ensureEnd'], |
|
// http://usejsdoc.org/tags-inner.html |
|
'inner': ['ensureEnd'], |
|
// http://usejsdoc.org/tags-instance.html |
|
'instance': ['ensureEnd'], |
|
// http://usejsdoc.org/tags-kind.html |
|
'kind': ['parseKind'], |
|
// http://usejsdoc.org/tags-mixes.html |
|
'mixes': ['parseNamePath', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-mixin.html |
|
'mixin': ['parseNamePathOptional', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-member.html |
|
'member': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-method.html |
|
'method': ['parseNamePathOptional', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-module.html |
|
'module': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// Synonym: http://usejsdoc.org/tags-method.html |
|
'func': ['parseNamePathOptional', 'ensureEnd'], |
|
// Synonym: http://usejsdoc.org/tags-method.html |
|
'function': ['parseNamePathOptional', 'ensureEnd'], |
|
// Synonym: http://usejsdoc.org/tags-member.html |
|
'var': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-name.html |
|
'name': ['parseNamePath', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-namespace.html |
|
'namespace': ['parseType', 'parseNamePathOptional', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-private.html |
|
'private': ['parseType', 'parseDescription'], |
|
// http://usejsdoc.org/tags-protected.html |
|
'protected': ['parseType', 'parseDescription'], |
|
// http://usejsdoc.org/tags-public.html |
|
'public': ['parseType', 'parseDescription'], |
|
// http://usejsdoc.org/tags-readonly.html |
|
'readonly': ['ensureEnd'], |
|
// http://usejsdoc.org/tags-requires.html |
|
'requires': ['parseNamePath', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-since.html |
|
'since': ['parseDescription'], |
|
// http://usejsdoc.org/tags-static.html |
|
'static': ['ensureEnd'], |
|
// http://usejsdoc.org/tags-summary.html |
|
'summary': ['parseDescription'], |
|
// http://usejsdoc.org/tags-this.html |
|
'this': ['parseThis', 'ensureEnd'], |
|
// http://usejsdoc.org/tags-todo.html |
|
'todo': ['parseDescription'], |
|
// http://usejsdoc.org/tags-typedef.html |
|
'typedef': ['parseType', 'parseNamePathOptional'], |
|
// http://usejsdoc.org/tags-variation.html |
|
'variation': ['parseVariation'], |
|
// http://usejsdoc.org/tags-version.html |
|
'version': ['parseDescription'] |
|
}; |
|
|
|
TagParser.prototype.parse = function parse() { |
|
var i, iz, sequences, method; |
|
|
|
|
|
// empty title |
|
if (!this._title) { |
|
if (!this.addError('Missing or invalid title')) { |
|
return null; |
|
} |
|
} |
|
|
|
// Seek to content last index. |
|
this._last = seekContent(this._title); |
|
|
|
if (this._options.range) { |
|
this._tag.range = [this._first, source.slice(0, this._last).replace(/\s*$/, '').length].map(convertIndex); |
|
} |
|
|
|
if (hasOwnProperty(Rules, this._title)) { |
|
sequences = Rules[this._title]; |
|
} else { |
|
// default sequences |
|
sequences = ['parseType', 'parseName', 'parseDescription', 'epilogue']; |
|
} |
|
|
|
for (i = 0, iz = sequences.length; i < iz; ++i) { |
|
method = sequences[i]; |
|
if (!this[method]()) { |
|
return null; |
|
} |
|
} |
|
|
|
return this._tag; |
|
}; |
|
|
|
function parseTag(options) { |
|
var title, parser, tag; |
|
|
|
// skip to tag |
|
if (!skipToTag()) { |
|
return null; |
|
} |
|
|
|
// scan title |
|
title = scanTitle(); |
|
|
|
// construct tag parser |
|
parser = new TagParser(options, title); |
|
tag = parser.parse(); |
|
|
|
// Seek global index to end of this tag. |
|
while (index < parser._last) { |
|
advance(); |
|
} |
|
|
|
return tag; |
|
} |
|
|
|
// |
|
// Parse JSDoc |
|
// |
|
|
|
function scanJSDocDescription(preserveWhitespace) { |
|
var description = '', ch, atAllowed; |
|
|
|
atAllowed = true; |
|
while (index < length) { |
|
ch = source.charCodeAt(index); |
|
|
|
if (atAllowed && ch === 0x40 /* '@' */) { |
|
break; |
|
} |
|
|
|
if (esutils.code.isLineTerminator(ch)) { |
|
atAllowed = true; |
|
} else if (atAllowed && !esutils.code.isWhiteSpace(ch)) { |
|
atAllowed = false; |
|
} |
|
|
|
description += advance(); |
|
} |
|
|
|
return preserveWhitespace ? description : description.trim(); |
|
} |
|
|
|
function parse(comment, options) { |
|
var tags = [], tag, description, interestingTags, i, iz; |
|
|
|
if (options === undefined) { |
|
options = {}; |
|
} |
|
|
|
if (typeof options.unwrap === 'boolean' && options.unwrap) { |
|
source = unwrapComment(comment); |
|
} else { |
|
source = comment; |
|
} |
|
|
|
originalSource = comment; |
|
|
|
// array of relevant tags |
|
if (options.tags) { |
|
if (Array.isArray(options.tags)) { |
|
interestingTags = { }; |
|
for (i = 0, iz = options.tags.length; i < iz; i++) { |
|
if (typeof options.tags[i] === 'string') { |
|
interestingTags[options.tags[i]] = true; |
|
} else { |
|
utility.throwError('Invalid "tags" parameter: ' + options.tags); |
|
} |
|
} |
|
} else { |
|
utility.throwError('Invalid "tags" parameter: ' + options.tags); |
|
} |
|
} |
|
|
|
length = source.length; |
|
index = 0; |
|
lineNumber = 0; |
|
recoverable = options.recoverable; |
|
sloppy = options.sloppy; |
|
strict = options.strict; |
|
|
|
description = scanJSDocDescription(options.preserveWhitespace); |
|
|
|
while (true) { |
|
tag = parseTag(options); |
|
if (!tag) { |
|
break; |
|
} |
|
if (!interestingTags || interestingTags.hasOwnProperty(tag.title)) { |
|
tags.push(tag); |
|
} |
|
} |
|
|
|
return { |
|
description: description, |
|
tags: tags |
|
}; |
|
} |
|
exports.parse = parse; |
|
}(jsdoc = {})); |
|
|
|
exports.version = utility.VERSION; |
|
exports.parse = jsdoc.parse; |
|
exports.parseType = typed.parseType; |
|
exports.parseParamType = typed.parseParamType; |
|
exports.unwrapComment = unwrapComment; |
|
exports.Syntax = shallowCopy(typed.Syntax); |
|
exports.Error = utility.DoctrineError; |
|
exports.type = { |
|
Syntax: exports.Syntax, |
|
parseType: typed.parseType, |
|
parseParamType: typed.parseParamType, |
|
stringify: typed.stringify |
|
}; |
|
}()); |
|
/* vim: set sw=4 ts=4 et tw=80 : */
|
|
|