/node_modules/jsdoc/lib/jsdoc/tag/type.js
JavaScript | 311 lines | 185 code | 38 blank | 88 comment | 26 complexity | 2c1e3f2949957e43ad5de1f7c425478a MD5 | raw file
- /**
- * @module jsdoc/tag/type
- *
- * @author Michael Mathews <micmath@gmail.com>
- * @author Jeff Williams <jeffrey.l.williams@gmail.com>
- * @license Apache License 2.0 - See file 'LICENSE.md' in this project.
- */
- 'use strict';
- var catharsis = require('catharsis');
- var jsdoc = {
- name: require('jsdoc/name'),
- tag: {
- inline: require('jsdoc/tag/inline')
- },
- util: {
- cast: require('jsdoc/util/cast')
- }
- };
- var util = require('util');
- /**
- * Information about a type expression extracted from tag text.
- *
- * @typedef TypeExpressionInfo
- * @memberof module:jsdoc/tag/type
- * @property {string} expression - The type expression.
- * @property {string} text - The updated tag text.
- */
- /** @private */
- function unescapeBraces(text) {
- return text.replace(/\\\{/g, '{')
- .replace(/\\\}/g, '}');
- }
- /**
- * Extract a type expression from the tag text.
- *
- * @private
- * @param {string} string - The tag text.
- * @return {module:jsdoc/tag/type.TypeExpressionInfo} The type expression and updated tag text.
- */
- function extractTypeExpression(string) {
- var completeExpression;
- var count = 0;
- var position = 0;
- var expression = '';
- var startIndex = string.search(/\{[^@]/);
- var textStartIndex;
- if (startIndex !== -1) {
- // advance to the first character in the type expression
- position = textStartIndex = startIndex + 1;
- count++;
- while (position < string.length) {
- switch (string[position]) {
- case '\\':
- // backslash is an escape character, so skip the next character
- position++;
- break;
- case '{':
- count++;
- break;
- case '}':
- count--;
- break;
- default:
- // do nothing
- }
- if (count === 0) {
- completeExpression = string.slice(startIndex, position + 1);
- expression = string.slice(textStartIndex, position).trim();
- break;
- }
- position++;
- }
- }
- string = completeExpression ? string.replace(completeExpression, '') : string;
- return {
- expression: unescapeBraces(expression),
- newString: string.trim()
- };
- }
- /** @private */
- function getTagInfo(tagValue, canHaveName, canHaveType) {
- var name = '';
- var typeExpression = '';
- var text = tagValue;
- var expressionAndText;
- var nameAndDescription;
- var typeOverride;
- if (canHaveType) {
- expressionAndText = extractTypeExpression(text);
- typeExpression = expressionAndText.expression;
- text = expressionAndText.newString;
- }
- if (canHaveName) {
- nameAndDescription = jsdoc.name.splitName(text);
- name = nameAndDescription.name;
- text = nameAndDescription.description;
- }
- // an inline @type tag, like {@type Foo}, overrides the type expression
- if (canHaveType) {
- typeOverride = jsdoc.tag.inline.extractInlineTag(text, 'type');
- if (typeOverride.tags && typeOverride.tags[0]) {
- typeExpression = typeOverride.tags[0].text;
- }
- text = typeOverride.newString;
- }
- return {
- name: name,
- typeExpression: typeExpression,
- text: text
- };
- }
- /**
- * Information provided in a JSDoc tag.
- *
- * @typedef {Object} TagInfo
- * @memberof module:jsdoc/tag/type
- * @property {string} TagInfo.defaultvalue - The default value of the member.
- * @property {string} TagInfo.name - The name of the member (for example, `myParamName`).
- * @property {boolean} TagInfo.nullable - Indicates whether the member can be set to `null` or
- * `undefined`.
- * @property {boolean} TagInfo.optional - Indicates whether the member is optional.
- * @property {string} TagInfo.text - Descriptive text for the member (for example, `The user's email
- * address.`).
- * @property {Array.<string>} TagInfo.type - The type or types that the member can contain (for
- * example, `string` or `MyNamespace.MyClass`).
- * @property {string} TagInfo.typeExpression - The type expression that was parsed to identify the
- * types.
- * @property {boolean} TagInfo.variable - Indicates whether the number of members that are provided
- * can vary (for example, in a function that accepts any number of parameters).
- */
- // TODO: move to module:jsdoc/name?
- /**
- * Extract JSDoc-style type information from the name specified in the tag info, including the
- * member name; whether the member is optional; and the default value of the member.
- *
- * @private
- * @param {module:jsdoc/tag/type.TagInfo} tagInfo - Information contained in the tag.
- * @return {module:jsdoc/tag/type.TagInfo} Updated information from the tag.
- */
- function parseName(tagInfo) {
- // like '[foo]' or '[ foo ]' or '[foo=bar]' or '[ foo=bar ]' or '[ foo = bar ]'
- // or 'foo=bar' or 'foo = bar'
- if ( /^(\[)?\s*(.+?)\s*(\])?$/.test(tagInfo.name) ) {
- tagInfo.name = RegExp.$2;
- // were the "optional" brackets present?
- if (RegExp.$1 && RegExp.$3) {
- tagInfo.optional = true;
- }
- // like 'foo=bar' or 'foo = bar'
- if ( /^(.+?)\s*=\s*(.+)$/.test(tagInfo.name) ) {
- tagInfo.name = RegExp.$1;
- tagInfo.defaultvalue = jsdoc.util.cast.cast(RegExp.$2);
- }
- }
- return tagInfo;
- }
- /** @private */
- function getTypeStrings(parsedType, isOutermostType) {
- var applications;
- var typeString;
- var types = [];
- var TYPES = catharsis.Types;
- switch (parsedType.type) {
- case TYPES.AllLiteral:
- types.push('*');
- break;
- case TYPES.FunctionType:
- types.push('function');
- break;
- case TYPES.NameExpression:
- types.push(parsedType.name);
- break;
- case TYPES.NullLiteral:
- types.push('null');
- break;
- case TYPES.RecordType:
- types.push('Object');
- break;
- case TYPES.TypeApplication:
- // if this is the outermost type, we strip the modifiers; otherwise, we keep them
- if (isOutermostType) {
- applications = parsedType.applications.map(function(application) {
- return catharsis.stringify(application);
- }).join(', ');
- typeString = util.format( '%s.<%s>', getTypeStrings(parsedType.expression),
- applications );
- types.push(typeString);
- }
- else {
- types.push( catharsis.stringify(parsedType) );
- }
- break;
- case TYPES.TypeUnion:
- parsedType.elements.forEach(function(element) {
- types = types.concat( getTypeStrings(element) );
- });
- break;
- case TYPES.UndefinedLiteral:
- types.push('undefined');
- break;
- case TYPES.UnknownLiteral:
- types.push('?');
- break;
- default:
- // this shouldn't happen
- throw new Error( util.format('unrecognized type %s in parsed type: %j', parsedType.type,
- parsedType) );
- }
- return types;
- }
- /**
- * Extract JSDoc-style and Closure Compiler-style type information from the type expression
- * specified in the tag info.
- *
- * @private
- * @param {module:jsdoc/tag/type.TagInfo} tagInfo - Information contained in the tag.
- * @return {module:jsdoc/tag/type.TagInfo} Updated information from the tag.
- */
- function parseTypeExpression(tagInfo) {
- var errorMessage;
- var parsedType;
- // don't try to parse empty type expressions
- if (!tagInfo.typeExpression) {
- return tagInfo;
- }
- try {
- parsedType = catharsis.parse(tagInfo.typeExpression, {jsdoc: true});
- }
- catch (e) {
- // always re-throw so the caller has a chance to report which file was bad
- throw new Error( util.format('Invalid type expression "%s": %s', tagInfo.typeExpression,
- e.message) );
- }
- tagInfo.type = tagInfo.type.concat( getTypeStrings(parsedType, true) );
- tagInfo.parsedType = parsedType;
- // Catharsis and JSDoc use the same names for 'optional' and 'nullable'...
- ['optional', 'nullable'].forEach(function(key) {
- if (parsedType[key] !== null && parsedType[key] !== undefined) {
- tagInfo[key] = parsedType[key];
- }
- });
- // ...but not 'variable'.
- if (parsedType.repeatable !== null && parsedType.repeatable !== undefined) {
- tagInfo.variable = parsedType.repeatable;
- }
- return tagInfo;
- }
- // TODO: allow users to add/remove type parsers (perhaps via plugins)
- var typeParsers = [parseName, parseTypeExpression];
- /**
- * Parse the value of a JSDoc tag.
- *
- * @param {string} tagValue - The value of the tag. For example, the tag `@param {string} name` has
- * a value of `{string} name`.
- * @param {boolean} canHaveName - Indicates whether the value can include a symbol name.
- * @param {boolean} canHaveType - Indicates whether the value can include a type expression that
- * describes the symbol.
- * @return {module:jsdoc/tag/type.TagInfo} Information obtained from the tag.
- * @throws {Error} Thrown if a type expression cannot be parsed.
- */
- exports.parse = function(tagValue, canHaveName, canHaveType) {
- if (typeof tagValue !== 'string') { tagValue = ''; }
- var tagInfo = getTagInfo(tagValue, canHaveName, canHaveType);
- tagInfo.type = tagInfo.type || [];
- typeParsers.forEach(function(parser) {
- tagInfo = parser.call(this, tagInfo);
- });
- // if we wanted a type, but the parsers didn't add any type names, use the type expression
- if (canHaveType && !tagInfo.type.length && tagInfo.typeExpression) {
- tagInfo.type = [tagInfo.typeExpression];
- }
- return tagInfo;
- };