jsdoc
Version:
An API documentation generator for JavaScript.
1,090 lines (1,003 loc) • 29.4 kB
JavaScript
/**
* Define tags that are known in JSDoc.
* @module jsdoc/tag/dictionary/definitions
*/
const _ = require('underscore');
const jsdoc = {
env: require('jsdoc/env'),
name: require('jsdoc/name'),
src: {
astnode: require('jsdoc/src/astnode')
},
tag: {
inline: require('jsdoc/tag/inline'),
type: require('jsdoc/tag/type')
},
util: {
doop: require('jsdoc/util/doop'),
logger: require('jsdoc/util/logger')
}
};
const path = require('jsdoc/path');
const Syntax = require('jsdoc/src/syntax').Syntax;
const hasOwnProp = Object.prototype.hasOwnProperty;
const DEFINITIONS = {
closure: 'closureTags',
jsdoc: 'jsdocTags'
};
const MODULE_NAMESPACE = 'module:';
// Clone a tag definition, excluding synonyms.
function cloneTagDef(tagDef, extras) {
const newTagDef = jsdoc.util.doop(tagDef);
delete newTagDef.synonyms;
return (extras ? _.extend(newTagDef, extras) : newTagDef);
}
function getSourcePaths() {
const sourcePaths = jsdoc.env.sourceFiles.slice(0) || [];
if (jsdoc.env.opts._) {
jsdoc.env.opts._.forEach(sourcePath => {
const resolved = path.resolve(jsdoc.env.pwd, sourcePath);
if (!sourcePaths.includes(resolved)) {
sourcePaths.push(resolved);
}
});
}
return sourcePaths;
}
function filepathMinusPrefix(filepath) {
const sourcePaths = getSourcePaths();
const commonPrefix = path.commonPrefix(sourcePaths);
let result = '';
if (filepath) {
filepath = path.normalize(filepath);
// always use forward slashes in the result
result = (filepath + path.sep).replace(commonPrefix, '')
.replace(/\\/g, '/');
}
if (result.length > 0 && result[result.length - 1] !== '/') {
result += '/';
}
return result;
}
/** @private */
function setDocletKindToTitle(doclet, {title}) {
doclet.addTag( 'kind', title );
}
function setDocletScopeToTitle(doclet, {title}) {
try {
doclet.setScope(title);
}
catch (e) {
jsdoc.util.logger.error(e.message);
}
}
function setDocletNameToValue(doclet, {value, text}) {
if (value && value.description) { // as in a long tag
doclet.addTag('name', value.description);
}
else if (text) { // or a short tag
doclet.addTag('name', text);
}
}
function setDocletNameToValueName(doclet, {value}) {
if (value && value.name) {
doclet.addTag('name', value.name);
}
}
function setDocletDescriptionToValue(doclet, {value}) {
if (value) {
doclet.addTag('description', value);
}
}
function setDocletTypeToValueType(doclet, {value}) {
if (value && value.type) {
// Add the type names and other type properties (such as `optional`).
// Don't overwrite existing properties.
Object.keys(value).forEach(prop => {
if ( !hasOwnProp.call(doclet, prop) ) {
doclet[prop] = value[prop];
}
});
}
}
function setNameToFile(doclet) {
let name;
if (doclet.meta.filename) {
name = filepathMinusPrefix(doclet.meta.path) + doclet.meta.filename;
doclet.addTag('name', name);
}
}
function setDocletMemberof(doclet, {value}) {
if (value && value !== '<global>') {
doclet.setMemberof(value);
}
}
function applyNamespace(docletOrNs, tag) {
if (typeof docletOrNs === 'string') { // ns
tag.value = jsdoc.name.applyNamespace(tag.value, docletOrNs);
}
else { // doclet
if (!docletOrNs.name) {
return; // error?
}
docletOrNs.longname = jsdoc.name.applyNamespace(docletOrNs.name, tag.title);
}
}
function setDocletNameToFilename(doclet) {
let name = '';
if (doclet.meta.path) {
name = filepathMinusPrefix(doclet.meta.path);
}
name += doclet.meta.filename.replace(/\.js$/i, '');
doclet.name = name;
}
function parseTypeText(text) {
const tagType = jsdoc.tag.type.parse(text, false, true);
return tagType.typeExpression || text;
}
function parseBorrows(doclet, {text}) {
const m = /^([\s\S]+?)(?:\s+as\s+([\s\S]+))?$/.exec(text);
if (m) {
if (m[1] && m[2]) {
return {
target: m[1],
source: m[2]
};
}
else if (m[1]) {
return {
target: m[1]
};
}
return {};
} else {
return {};
}
}
function stripModuleNamespace(name) {
return name.replace(/^module:/, '');
}
function firstWordOf(string) {
const m = /^(\S+)/.exec(string);
if (m) {
return m[1];
}
else {
return '';
}
}
function combineTypes({value}) {
let combined;
if (value && value.type) {
if (value.type.names.length === 1) {
combined = value.type.names[0];
}
else {
combined = `(${value.type.names.join('|')})`;
}
}
return combined;
}
// Tags that JSDoc uses internally, and that must always be defined.
const internalTags = {
// Special separator tag indicating that multiple doclets should be generated for the same
// comment. Used internally (and by some JSDoc users, although it's not officially supported).
// In the following example, the parser will replace `//**` with an `@also` tag:
// /**
// * Foo.
// *//**
// * Foo with a param.
// * @param {string} bar
// */
// function foo(bar) {}
also: {
onTagged() {
// let the parser handle it; we define the tag here to avoid "not a known tag" errors
}
},
description: {
mustHaveValue: true,
synonyms: ['desc']
},
kind: {
mustHaveValue: true
},
name: {
mustHaveValue: true
},
undocumented: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.undocumented = true;
doclet.comment = '';
}
}
};
// Core JSDoc tags that are shared with other tag dictionaries.
let baseTags = exports.baseTags = {
abstract: {
mustNotHaveValue: true,
onTagged(doclet) {
// we call this `virtual` because `abstract` is a reserved word
doclet.virtual = true;
},
synonyms: ['virtual']
},
access: {
mustHaveValue: true,
onTagged(doclet, {value}) {
// only valid values are package, private, protected and public
if ( /^(package|private|protected|public)$/i.test(value) ) {
doclet.access = value.toLowerCase();
}
else {
delete doclet.access;
}
}
},
alias: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.alias = value;
}
},
async: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.async = true;
}
},
augments: {
mustHaveValue: true,
// Allow augments value to be specified as a normal type, e.g. {Type}
onTagText: parseTypeText,
onTagged(doclet, {value}) {
doclet.augment( firstWordOf(value) );
},
synonyms: ['extends']
},
author: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.author = doclet.author || [];
doclet.author.push(value);
}
},
// this symbol has a member that should use the same docs as another symbol
borrows: {
mustHaveValue: true,
onTagged(doclet, tag) {
const borrows = parseBorrows(doclet, tag);
doclet.borrow(borrows.target, borrows.source);
}
},
class: {
onTagged(doclet, tag) {
let looksLikeDesc;
doclet.addTag('kind', 'class');
// handle special case where both @class and @constructor tags exist in same doclet
if (tag.originalTitle === 'class') {
// multiple words after @class?
looksLikeDesc = (tag.value || '').match(/\S+\s+\S+/);
if ((looksLikeDesc || /@construct(s|or)\b/i.test(doclet.comment)) &&
!/@classdesc\b/i.test(doclet.comment)) {
// treat the @class tag as a @classdesc tag instead
doclet.classdesc = tag.value;
return;
}
}
setDocletNameToValue(doclet, tag);
},
synonyms: ['constructor']
},
classdesc: {
onTagged(doclet, {value}) {
doclet.classdesc = value;
}
},
constant: {
canHaveType: true,
canHaveName: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValueName(doclet, tag);
setDocletTypeToValueType(doclet, tag);
},
synonyms: ['const']
},
constructs: {
onTagged(doclet, {value}) {
let ownerClassName;
if (!value) {
// this can be resolved later in the handlers
ownerClassName = '{@thisClass}';
}
else {
ownerClassName = firstWordOf(value);
}
doclet.addTag('alias', ownerClassName);
doclet.addTag('kind', 'class');
}
},
copyright: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.copyright = value;
}
},
default: {
onTagged(doclet, {value}) {
const nodeToValue = jsdoc.src.astnode.nodeToValue;
if (value) {
doclet.defaultvalue = value;
}
else if (doclet.meta && doclet.meta.code &&
typeof doclet.meta.code.value !== 'undefined') {
switch (doclet.meta.code.type) {
case Syntax.ArrayExpression:
doclet.defaultvalue = nodeToValue(doclet.meta.code.node);
doclet.defaultvaluetype = 'array';
break;
case Syntax.Literal:
doclet.defaultvalue = doclet.meta.code.value;
break;
case Syntax.ObjectExpression:
doclet.defaultvalue = nodeToValue(doclet.meta.code.node);
doclet.defaultvaluetype = 'object';
break;
default:
// do nothing
break;
}
}
},
synonyms: ['defaultvalue']
},
deprecated: {
// value is optional
onTagged(doclet, {value}) {
doclet.deprecated = value || true;
}
},
enum: {
canHaveType: true,
onTagged(doclet, tag) {
doclet.kind = doclet.kind || 'member';
doclet.isEnum = true;
setDocletTypeToValueType(doclet, tag);
}
},
event: {
isNamespace: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
}
},
example: {
keepsWhitespace: true,
removesIndent: true,
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.examples = doclet.examples || [];
doclet.examples.push(value);
}
},
exports: {
mustHaveValue: true,
onTagged(doclet, {value}) {
const modName = firstWordOf(value);
// in case the user wrote something like `/** @exports module:foo */`:
doclet.addTag( 'alias', stripModuleNamespace(modName) );
doclet.addTag('kind', 'module');
}
},
external: {
canHaveType: true,
isNamespace: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
doclet.addTag('name', doclet.type.names[0]);
}
else {
setDocletNameToValue(doclet, tag);
}
},
synonyms: ['host']
},
file: {
onTagged(doclet, tag) {
setNameToFile(doclet);
setDocletKindToTitle(doclet, tag);
setDocletDescriptionToValue(doclet, tag);
doclet.preserveName = true;
},
synonyms: ['fileoverview', 'overview']
},
fires: {
mustHaveValue: true,
onTagged(doclet, tag) {
doclet.fires = doclet.fires || [];
applyNamespace('event', tag);
doclet.fires.push(tag.value);
},
synonyms: ['emits']
},
function: {
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
},
synonyms: ['func', 'method']
},
generator: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.generator = true;
}
},
global: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.scope = jsdoc.name.SCOPE.NAMES.GLOBAL;
delete doclet.memberof;
}
},
hideconstructor: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.hideconstructor = true;
}
},
ignore: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.ignore = true;
}
},
implements: {
mustHaveValue: true,
onTagText: parseTypeText,
onTagged(doclet, {value}) {
doclet.implements = doclet.implements || [];
doclet.implements.push(value);
}
},
inheritdoc: {
mustNotHaveValue: true,
onTagged(doclet) {
// use an empty string so JSDoc can support `@inheritdoc Foo#bar` in the future
doclet.inheritdoc = '';
}
},
inner: {
onTagged(doclet, tag) {
setDocletScopeToTitle(doclet, tag);
}
},
instance: {
onTagged(doclet, tag) {
setDocletScopeToTitle(doclet, tag);
}
},
interface: {
canHaveName: true,
onTagged(doclet, tag) {
doclet.addTag('kind', 'interface');
if (tag.value) {
setDocletNameToValueName(doclet, tag);
}
}
},
lends: {
onTagged(doclet, {value}) {
doclet.alias = value || jsdoc.name.LONGNAMES.GLOBAL;
doclet.addTag('undocumented');
}
},
license: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.license = value;
}
},
listens: {
mustHaveValue: true,
onTagged(doclet, tag) {
doclet.listens = doclet.listens || [];
applyNamespace('event', tag);
doclet.listens.push(tag.value);
}
},
member: {
canHaveType: true,
canHaveName: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValueName(doclet, tag);
setDocletTypeToValueType(doclet, tag);
},
synonyms: ['var']
},
memberof: {
mustHaveValue: true,
onTagged(doclet, tag) {
if (tag.originalTitle === 'memberof!') {
doclet.forceMemberof = true;
if (tag.value === jsdoc.name.LONGNAMES.GLOBAL) {
doclet.addTag('global');
delete doclet.memberof;
}
}
setDocletMemberof(doclet, tag);
},
synonyms: ['memberof!']
},
// this symbol mixes in all of the specified object's members
mixes: {
mustHaveValue: true,
onTagged(doclet, {value}) {
const source = firstWordOf(value);
doclet.mix(source);
}
},
mixin: {
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
}
},
modifies: {
canHaveType: true,
onTagged(doclet, {value}) {
doclet.modifies = doclet.modifies || [];
doclet.modifies.push(value);
}
},
module: {
canHaveType: true,
isNamespace: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
if (!doclet.name) {
setDocletNameToFilename(doclet);
}
// in case the user wrote something like `/** @module module:foo */`:
doclet.name = stripModuleNamespace(doclet.name);
setDocletTypeToValueType(doclet, tag);
}
},
namespace: {
canHaveType: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletNameToValue(doclet, tag);
setDocletTypeToValueType(doclet, tag);
}
},
package: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.access = 'package';
}
},
param: {
canHaveType: true,
canHaveName: true,
onTagged(doclet, {value}) {
doclet.params = doclet.params || [];
doclet.params.push(value || {});
},
synonyms: ['arg', 'argument']
},
private: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.access = 'private';
}
},
property: {
mustHaveValue: true,
canHaveType: true,
canHaveName: true,
onTagged(doclet, {value}) {
doclet.properties = doclet.properties || [];
doclet.properties.push(value);
},
synonyms: ['prop']
},
protected: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.access = 'protected';
}
},
public: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.access = 'public';
}
},
readonly: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.readonly = true;
}
},
requires: {
mustHaveValue: true,
onTagged(doclet, {value}) {
let requiresName;
// inline link tags are passed through as-is so that `@requires {@link foo}` works
if ( jsdoc.tag.inline.isInlineTag(value, 'link\\S*') ) {
requiresName = value;
}
// otherwise, assume it's a module
else {
requiresName = firstWordOf(value);
if (requiresName.indexOf(MODULE_NAMESPACE) !== 0) {
requiresName = MODULE_NAMESPACE + requiresName;
}
}
doclet.requires = doclet.requires || [];
doclet.requires.push(requiresName);
}
},
returns: {
mustHaveValue: true,
canHaveType: true,
onTagged(doclet, {value}) {
doclet.returns = doclet.returns || [];
doclet.returns.push(value);
},
synonyms: ['return']
},
see: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.see = doclet.see || [];
doclet.see.push(value);
}
},
since: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.since = value;
}
},
static: {
onTagged(doclet, tag) {
setDocletScopeToTitle(doclet, tag);
}
},
summary: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.summary = value;
}
},
'this': {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.this = firstWordOf(value);
}
},
todo: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.todo = doclet.todo || [];
doclet.todo.push(value);
}
},
throws: {
mustHaveValue: true,
canHaveType: true,
onTagged(doclet, {value}) {
doclet.exceptions = doclet.exceptions || [];
doclet.exceptions.push(value);
},
synonyms: ['exception']
},
tutorial: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.tutorials = doclet.tutorials || [];
doclet.tutorials.push(value);
}
},
type: {
mustHaveValue: true,
mustNotHaveDescription: true,
canHaveType: true,
onTagText(text) {
let closeIdx;
let openIdx;
const OPEN_BRACE = '{';
const CLOSE_BRACE = '}';
// remove line breaks
text = text.replace(/[\f\n\r]/g, '');
// Text must be a type expression; for backwards compatibility, we add braces if they're
// missing. But do NOT add braces to things like `@type {string} some pointless text`.
openIdx = text.indexOf(OPEN_BRACE);
closeIdx = text.indexOf(CLOSE_BRACE);
// a type expression is at least one character long
if ( openIdx !== 0 || closeIdx <= openIdx + 1) {
text = OPEN_BRACE + text + CLOSE_BRACE;
}
return text;
},
onTagged(doclet, tag) {
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
// for backwards compatibility, we allow @type for functions to imply return type
if (doclet.kind === 'function') {
doclet.addTag('returns', tag.text);
}
}
}
},
typedef: {
canHaveType: true,
canHaveName: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
if (tag.value) {
setDocletNameToValueName(doclet, tag);
// callbacks are always type {function}
if (tag.originalTitle === 'callback') {
doclet.type = {
names: [
'function'
]
};
}
else {
setDocletTypeToValueType(doclet, tag);
}
}
},
synonyms: ['callback']
},
variation: {
mustHaveValue: true,
onTagged(doclet, tag) {
let value = tag.value;
if ( /^\((.+)\)$/.test(value) ) {
value = RegExp.$1;
}
doclet.variation = value;
}
},
version: {
mustHaveValue: true,
onTagged(doclet, {value}) {
doclet.version = value;
}
},
yields: {
mustHaveValue: true,
canHaveType: true,
onTagged(doclet, {value}) {
doclet.yields = doclet.yields || [];
doclet.yields.push(value);
},
synonyms: ['yield']
}
};
baseTags = _.extend(baseTags, internalTags);
// Tag dictionary for JSDoc.
exports.jsdocTags = baseTags;
function ignore() {
// do nothing
}
// Tag dictionary for Google Closure Compiler.
exports.closureTags = {
const: {
canHaveType: true,
onTagged(doclet, tag) {
doclet.kind = 'constant';
setDocletTypeToValueType(doclet, tag);
},
// Closure Compiler only
synonyms: ['define']
},
constructor: cloneTagDef(baseTags.class),
deprecated: cloneTagDef(baseTags.deprecated),
// Closure Compiler only
dict: {
onTagged: ignore
},
enum: cloneTagDef(baseTags.enum),
// Closure Compiler only
export: {
onTagged: ignore
},
// Closure Compiler only
externs: {
onTagged: ignore
},
extends: cloneTagDef(baseTags.augments),
fileoverview: {
onTagged(doclet, tag) {
setNameToFile(doclet);
doclet.kind = 'file';
setDocletDescriptionToValue(doclet, tag);
doclet.preserveName = true;
}
},
final: cloneTagDef(baseTags.readonly),
implements: cloneTagDef(baseTags.implements),
// Closure Compiler only
implicitcast: {
onTagged: ignore
},
inheritdoc: cloneTagDef(baseTags.inheritdoc),
interface: cloneTagDef(baseTags.interface, {
canHaveName: false,
mustNotHaveValue: true,
// Closure Compiler only
synonyms: ['record']
}),
lends: cloneTagDef(baseTags.lends),
license: cloneTagDef(baseTags.license),
modifies: cloneTagDef(baseTags.modifies),
// Closure Compiler only
noalias: {
onTagged: ignore
},
// Closure Compiler only
nocollapse: {
onTagged: ignore
},
// Closure Compiler only
nocompile: {
onTagged: ignore
},
// Closure Compiler only
nosideeffects: {
onTagged(doclet) {
doclet.modifies = [];
}
},
// Closure Compiler only
override: {
mustNotHaveValue: true,
onTagged(doclet) {
doclet.override = true;
}
},
package: {
canHaveType: true,
onTagged(doclet, tag) {
doclet.access = 'package';
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
}
}
},
param: cloneTagDef(baseTags.param),
// Closure Compiler only
polymer: {
onTagged: ignore
},
// Closure Compiler only
polymerBehavior: {
onTagged: ignore
},
// Closure Compiler only
preserve: cloneTagDef(baseTags.license),
private: {
canHaveType: true,
onTagged(doclet, tag) {
doclet.access = 'private';
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
}
}
},
protected: {
canHaveType: true,
onTagged(doclet, tag) {
doclet.access = 'protected';
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
}
}
},
public: {
canHaveType: true,
onTagged(doclet, tag) {
doclet.access = 'public';
if (tag.value && tag.value.type) {
setDocletTypeToValueType(doclet, tag);
}
}
},
return: cloneTagDef(baseTags.returns),
// Closure Compiler only
struct: {
onTagged: ignore
},
// Closure Compiler only
suppress: {
onTagged: ignore
},
// Closure Compiler only
template: {
onTagged: ignore
},
'this': {
canHaveType: true,
onTagged(doclet, tag) {
doclet.this = combineTypes(tag);
}
},
throws: cloneTagDef(baseTags.throws),
type: cloneTagDef(baseTags.type, {
mustNotHaveDescription: false
}),
typedef: {
canHaveType: true,
onTagged(doclet, tag) {
setDocletKindToTitle(doclet, tag);
setDocletTypeToValueType(doclet, tag);
}
},
// Closure Compiler only
unrestricted: {
onTagged: ignore
}
};
function addTagDefinitions(dictionary, tagDefs) {
Object.keys(tagDefs).forEach(tagName => {
let tagDef;
tagDef = tagDefs[tagName];
dictionary.defineTag(tagName, tagDef);
if (tagDef.synonyms) {
tagDef.synonyms.forEach(synonym => {
dictionary.defineSynonym(tagName, synonym);
});
}
});
}
/**
* Populate the given dictionary with the appropriate JSDoc tag definitions.
*
* If the `tagDefinitions` parameter is omitted, JSDoc uses its configuration settings to decide
* which tags to add to the dictionary.
*
* If the `tagDefinitions` parameter is included, JSDoc adds only the tag definitions from the
* `tagDefinitions` object. The configuration settings are ignored.
*
* @param {module:jsdoc/tag/dictionary} dictionary
* @param {Object} [tagDefinitions] - A dictionary whose values define the rules for a JSDoc tag.
*/
exports.defineTags = (dictionary, tagDefinitions) => {
let dictionaries;
if (!tagDefinitions) {
dictionaries = jsdoc.env.conf.tags.dictionaries;
if (!dictionaries) {
jsdoc.util.logger.error('The configuration setting "tags.dictionaries" is undefined. ' +
'Unable to load tag definitions.');
return;
}
else {
dictionaries = dictionaries.slice(0).reverse();
}
dictionaries.forEach(dictName => {
const tagDefs = exports[DEFINITIONS[dictName]];
if (!tagDefs) {
jsdoc.util.logger.error('The configuration setting "tags.dictionaries" contains ' +
'the unknown dictionary name %s. Ignoring the dictionary.', dictName);
return;
}
addTagDefinitions(dictionary, _.extend(tagDefs, internalTags));
});
}
else {
addTagDefinitions(dictionary, _.extend(tagDefinitions, internalTags));
}
};