diff --git a/lib/jsdoc/doclet.js b/lib/jsdoc/doclet.js index 5fe0e435..6786b156 100644 --- a/lib/jsdoc/doclet.js +++ b/lib/jsdoc/doclet.js @@ -217,7 +217,7 @@ Doclet.prototype.addTag = function(title, text) { }; function removeGlobal(longname) { - var globalRegexp = new RegExp('^' + jsdoc.name.GLOBAL_LONGNAME + '\\.?'); + var globalRegexp = new RegExp('^' + jsdoc.name.LONGNAMES.GLOBAL + '\\.?'); return longname.replace(globalRegexp, ''); } @@ -233,7 +233,7 @@ Doclet.prototype.setMemberof = function(sid) { * @type string */ this.memberof = removeGlobal(sid) - .replace(/\.prototype/g, jsdoc.name.INSTANCE); + .replace(/\.prototype/g, jsdoc.name.SCOPE.PUNC.INSTANCE); }; /** @@ -270,22 +270,22 @@ function getFilepath(doclet) { /** * Set the doclet's `scope` property. Must correspond to a scope name that is defined in - * {@link module:jsdoc/name.SCOPE_NAMES}. + * {@link module:jsdoc/name.SCOPE.NAMES}. * - * @param {module:jsdoc/name.SCOPE_NAMES} scope - The scope for the doclet relative to the symbol's + * @param {module:jsdoc/name.SCOPE.NAMES} scope - The scope for the doclet relative to the symbol's * parent. * @throws {Error} If the scope name is not recognized. */ Doclet.prototype.setScope = function(scope) { var errorMessage; var filepath; - var scopeNames = Object.keys(jsdoc.name.SCOPE_NAMES); + var scopeNames = _.values(jsdoc.name.SCOPE.NAMES); if (scopeNames.indexOf(scope) === -1) { filepath = getFilepath(this); - errorMessage = util.format('The scope name "%s" is not recognized. Use one of the names ' + - 'defined in module:jsdoc/name.SCOPE_NAMES.', scope); + errorMessage = util.format('The scope name "%s" is not recognized. Use one of the ' + + 'following values: %j', scope, scopeNames); if (filepath) { errorMessage += util.format(' (Source file: %s)', filepath); } diff --git a/lib/jsdoc/name.js b/lib/jsdoc/name.js index 447e5688..5cadacfe 100644 --- a/lib/jsdoc/name.js +++ b/lib/jsdoc/name.js @@ -8,30 +8,64 @@ var _ = require('underscore'); -// Longname used for doclets whose actual longname cannot be identified. -var ANONYMOUS_LONGNAME = exports.ANONYMOUS_LONGNAME = ''; -// Longname used for doclets in global scope. -var GLOBAL_LONGNAME = exports.GLOBAL_LONGNAME = ''; -var INNER = exports.INNER = '~'; -var INSTANCE = exports.INSTANCE = '#'; -var MODULE_PREFIX = exports.MODULE_PREFIX = 'module:'; -// Scope identifiers. -var SCOPE_NAMES = exports.SCOPE_NAMES = { - global: 'global', - inner: 'inner', - instance: 'instance', - 'static': 'static' +/** + * Longnames that have a special meaning in JSDoc. + * + * @enum {string} + * @static + * @memberof module:jsdoc/name + */ +var LONGNAMES = exports.LONGNAMES = { + /** Longname used for doclets that do not have a longname, such as anonymous functions. */ + ANONYMOUS: '', + /** Longname that represents global scope. */ + GLOBAL: '' }; -var STATIC = exports.STATIC = '.'; + +// TODO: Consider removing this rather than completing the list. In theory, any new tag can act as +// a namespace by setting its `isNamespace` attribute to `true`. +/** + * Namespaces that can be applied to a longname. + * + * @enum {string} + * @static + * @memberof module:jsdoc/name + */ +var NAMESPACES = exports.NAMESPACES = { + MODULE: 'module:' +}; + +/** + * Names and punctuation marks that identify doclet scopes. + * + * @enum {string} + * @static + * @memberof module:jsdoc/name + */ +var SCOPE = exports.SCOPE = { + NAMES: { + GLOBAL: 'global', + INNER: 'inner', + INSTANCE: 'instance', + STATIC: 'static' + }, + PUNC: { + INNER: '~', + INSTANCE: '#', + STATIC: '.' + } +}; + +// For backwards compatibility, this enum must use lower-case keys var scopeToPunc = exports.scopeToPunc = { - 'inner': INNER, - 'instance': INSTANCE, - 'static': STATIC + 'inner': SCOPE.PUNC.INNER, + 'instance': SCOPE.PUNC.INSTANCE, + 'static': SCOPE.PUNC.STATIC }; var puncToScope = exports.puncToScope = _.invert(scopeToPunc); -var DEFAULT_SCOPE = SCOPE_NAMES.static; -var REGEXP_SCOPE_PUNC = '([' + INNER + INSTANCE + STATIC + '])'; +var DEFAULT_SCOPE = SCOPE.NAMES.STATIC; +var REGEXP_SCOPE_PUNC = '([' + _.values(SCOPE.PUNC) + '])'; var REGEXP_LEADING_SCOPE = new RegExp('^' + REGEXP_SCOPE_PUNC); var REGEXP_TRAILING_SCOPE = new RegExp(REGEXP_SCOPE_PUNC + '$'); @@ -42,7 +76,7 @@ function nameIsLongname(name, memberof) { } function prototypeToPunc(name) { - return name.replace(/(?:^|\.)prototype\.?/g, INSTANCE); + return name.replace(/(?:^|\.)prototype\.?/g, SCOPE.PUNC.INSTANCE); } /** @@ -66,7 +100,7 @@ exports.resolve = function(doclet) { // member of a var in an outer scope? if (name && !memberof && doclet.meta.code && doclet.meta.code.funcscope) { - name = doclet.longname = doclet.meta.code.funcscope + INNER + name; + name = doclet.longname = doclet.meta.code.funcscope + SCOPE.PUNC.INNER + name; } if (memberof || doclet.forceMemberof) { // @memberof tag given @@ -78,7 +112,7 @@ exports.resolve = function(doclet) { } // the name and memberof are identical and refer to a module, // like @name module:foo, @memberof module:foo (probably a member like 'var exports') - else if (name && name === memberof && name.indexOf(MODULE_PREFIX) === 0) { + else if (name && name === memberof && name.indexOf(NAMESPACES.MODULE) === 0) { about = exports.shorten(name, (doclet.forceMemberof ? memberof : undefined)); } // the name and memberof are identical, like @name foo, @memberof foo @@ -117,7 +151,7 @@ exports.resolve = function(doclet) { delete doclet.memberof; } else if (about.scope) { - if (about.memberof === GLOBAL_LONGNAME) { // via @memberof ? + if (about.memberof === LONGNAMES.GLOBAL) { // via @memberof ? doclet.scope = 'global'; } else { diff --git a/lib/jsdoc/src/astnode.js b/lib/jsdoc/src/astnode.js index 4127f19e..f82254bb 100644 --- a/lib/jsdoc/src/astnode.js +++ b/lib/jsdoc/src/astnode.js @@ -10,7 +10,7 @@ var uid = 100000000; // TODO: docs // TODO: currently unused -var GLOBAL_NODE_ID = exports.GLOBAL_NODE_ID = require('jsdoc/name').GLOBAL_LONGNAME; +var GLOBAL_NODE_ID = exports.GLOBAL_NODE_ID = require('jsdoc/name').LONGNAMES.GLOBAL; /** * Check whether an AST node represents a function. diff --git a/lib/jsdoc/src/parser.js b/lib/jsdoc/src/parser.js index 43677801..9541219f 100644 --- a/lib/jsdoc/src/parser.js +++ b/lib/jsdoc/src/parser.js @@ -269,7 +269,7 @@ Parser.prototype.addDocletRef = function(e) { (node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression) && !this.refs[node.nodeId] ) { this.refs[node.nodeId] = { - longname: jsdoc.name.ANONYMOUS_LONGNAME, + longname: jsdoc.name.LONGNAMES.ANONYMOUS, meta: { code: e.code } @@ -323,10 +323,10 @@ Parser.prototype.astnodeToMemberof = function(node) { doclet = this._getDoclet(node.enclosingScope.nodeId); if (!doclet) { - result = jsdoc.name.ANONYMOUS_LONGNAME + jsdoc.name.INNER; + result = jsdoc.name.LONGNAMES.ANONYMOUS + jsdoc.name.SCOPE.PUNC.INNER; } else { - result = (doclet.longname || doclet.name) + jsdoc.name.INNER; + result = (doclet.longname || doclet.name) + jsdoc.name.SCOPE.PUNC.INNER; } } else { @@ -385,7 +385,7 @@ Parser.prototype.resolveThis = function(node) { doclet = this._getDoclet(node.enclosingScope.nodeId); if (!doclet) { - result = jsdoc.name.ANONYMOUS_LONGNAME; // TODO handle global this? + result = jsdoc.name.LONGNAMES.ANONYMOUS; // TODO handle global this? } else if (doclet['this']) { result = doclet['this']; diff --git a/lib/jsdoc/tag/dictionary/definitions.js b/lib/jsdoc/tag/dictionary/definitions.js index 66a3813b..be8c0951 100644 --- a/lib/jsdoc/tag/dictionary/definitions.js +++ b/lib/jsdoc/tag/dictionary/definitions.js @@ -24,8 +24,7 @@ var jsdoc = { var path = require('jsdoc/path'); var Syntax = require('jsdoc/src/syntax').Syntax; -var GLOBAL_LONGNAME = jsdoc.name.GLOBAL_LONGNAME; -var MODULE_PREFIX = jsdoc.name.MODULE_PREFIX; +var NAMESPACES = jsdoc.name.NAMESPACES; function getSourcePaths() { var sourcePaths = env.sourceFiles.slice(0) || []; @@ -474,7 +473,7 @@ exports.defineTags = function(dictionary) { dictionary.defineTag('lends', { onTagged: function(doclet, tag) { - doclet.alias = tag.value || GLOBAL_LONGNAME; + doclet.alias = tag.value || jsdoc.name.LONGNAMES.GLOBAL; doclet.addTag('undocumented'); } }); @@ -512,7 +511,7 @@ exports.defineTags = function(dictionary) { onTagged: function(doclet, tag) { if (tag.originalTitle === 'memberof!') { doclet.forceMemberof = true; - if (tag.value === GLOBAL_LONGNAME) { + if (tag.value === jsdoc.name.LONGNAMES.GLOBAL) { doclet.addTag('global'); delete doclet.memberof; } @@ -628,8 +627,8 @@ exports.defineTags = function(dictionary) { // otherwise, assume it's a module else { requiresName = firstWordOf(tag.value); - if (requiresName.indexOf(MODULE_PREFIX) !== 0) { - requiresName = MODULE_PREFIX + requiresName; + if (requiresName.indexOf(NAMESPACES.MODULE) !== 0) { + requiresName = NAMESPACES.MODULE + requiresName; } } diff --git a/lib/jsdoc/util/templateHelper.js b/lib/jsdoc/util/templateHelper.js index d86587ae..8c0f3d4e 100644 --- a/lib/jsdoc/util/templateHelper.js +++ b/lib/jsdoc/util/templateHelper.js @@ -9,6 +9,7 @@ var dictionary = require('jsdoc/tag/dictionary'); var util = require('util'); var hasOwnProp = Object.prototype.hasOwnProperty; +var NAMESPACES = require('jsdoc/name').NAMESPACES; var files = {}; @@ -446,10 +447,8 @@ var find = exports.find = function(data, spec) { * `false`. */ function isModuleExports(doclet) { - var MODULE_PREFIX = require('jsdoc/name').MODULE_PREFIX; - return doclet.longname && doclet.longname === doclet.name && - doclet.longname.indexOf(MODULE_PREFIX) === 0 && doclet.kind !== 'module'; + doclet.longname.indexOf(NAMESPACES.MODULE) === 0 && doclet.kind !== 'module'; } /** diff --git a/test/specs/jsdoc/doclet.js b/test/specs/jsdoc/doclet.js index f95a756b..edbbc5a2 100644 --- a/test/specs/jsdoc/doclet.js +++ b/test/specs/jsdoc/doclet.js @@ -3,6 +3,7 @@ describe('jsdoc/doclet', function() { // TODO: more tests + var _ = require('underscore'); var Doclet = require('jsdoc/doclet').Doclet; var debug = !!env.opts.debug; @@ -46,7 +47,7 @@ describe('jsdoc/doclet', function() { doclet.setScope(scopeName); } - Object.keys(require('jsdoc/name').SCOPE_NAMES).forEach(function(scopeName) { + _.values(require('jsdoc/name').SCOPE.NAMES).forEach(function(scopeName) { expect( setScope.bind(null, scopeName) ).not.toThrow(); }); }); diff --git a/test/specs/jsdoc/name.js b/test/specs/jsdoc/name.js index b5544ac1..f1fb9823 100644 --- a/test/specs/jsdoc/name.js +++ b/test/specs/jsdoc/name.js @@ -23,9 +23,9 @@ describe('jsdoc/name', function() { }); // TODO: add tests for other exported constants - it('should export a SCOPE_NAMES enum', function() { - expect(jsdoc.name.SCOPE_NAMES).toBeDefined(); - expect(typeof jsdoc.name.SCOPE_NAMES).toBe('object'); + it('should export a SCOPE enum', function() { + expect(jsdoc.name.SCOPE).toBeDefined(); + expect(typeof jsdoc.name.SCOPE).toBe('object'); }); it("should export a 'shorten' function", function() { @@ -38,27 +38,56 @@ describe('jsdoc/name', function() { expect(typeof jsdoc.name.splitName).toBe('function'); }); - describe('SCOPE_NAMES', function() { - var SCOPE_NAMES = jsdoc.name.SCOPE_NAMES; + describe('SCOPE', function() { + var SCOPE = jsdoc.name.SCOPE; - it('should have a "global" property', function() { - expect(SCOPE_NAMES.global).toBeDefined(); - expect(typeof SCOPE_NAMES.global).toBe('string'); + it('should have a "NAMES" property', function() { + expect(SCOPE.NAMES).toBeDefined(); + expect(typeof SCOPE.NAMES).toBe('object'); }); - it('should have an "inner" property', function() { - expect(SCOPE_NAMES.inner).toBeDefined(); - expect(typeof SCOPE_NAMES.inner).toBe('string'); + it('should have a "PUNC" property', function() { + expect(SCOPE.PUNC).toBeDefined(); + expect(typeof SCOPE.PUNC).toBe('object'); }); - it('should have an "instance" property', function() { - expect(SCOPE_NAMES.instance).toBeDefined(); - expect(typeof SCOPE_NAMES.instance).toBe('string'); + describe('NAMES', function() { + it('should have a "GLOBAL" property', function() { + expect(SCOPE.NAMES.GLOBAL).toBeDefined(); + expect(typeof SCOPE.NAMES.GLOBAL).toBe('string'); + }); + + it('should have an "INNER" property', function() { + expect(SCOPE.NAMES.INNER).toBeDefined(); + expect(typeof SCOPE.NAMES.INNER).toBe('string'); + }); + + it('should have an "INSTANCE" property', function() { + expect(SCOPE.NAMES.INSTANCE).toBeDefined(); + expect(typeof SCOPE.NAMES.INSTANCE).toBe('string'); + }); + + it('should have a "STATIC" property', function() { + expect(SCOPE.NAMES.STATIC).toBeDefined(); + expect(typeof SCOPE.NAMES.STATIC).toBe('string'); + }); }); - it('should have a "static" property', function() { - expect(SCOPE_NAMES.static).toBeDefined(); - expect(typeof SCOPE_NAMES.static).toBe('string'); + describe('PUNC', function() { + it('should have an "INNER" property', function() { + expect(SCOPE.PUNC.INNER).toBeDefined(); + expect(typeof SCOPE.PUNC.INNER).toBe('string'); + }); + + it('should have an "INSTANCE" property', function() { + expect(SCOPE.PUNC.INSTANCE).toBeDefined(); + expect(typeof SCOPE.PUNC.INSTANCE).toBe('string'); + }); + + it('should have a "STATIC" property', function() { + expect(SCOPE.PUNC.STATIC).toBeDefined(); + expect(typeof SCOPE.PUNC.STATIC).toBe('string'); + }); }); });