forked from lix-project/lix-website
899 lines
32 KiB
JavaScript
899 lines
32 KiB
JavaScript
|
/*
|
||
|
* @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 : */
|