From 3ae23b8b2138ee860eac73210e67e6adfb1c53e4 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Sun, 16 Feb 2014 07:44:28 -0800 Subject: [PATCH] provide a setter for doclet scope (#574) We previously set the doclet's scope by adding a `@scope` tag, which, in turn, was used to update the scope. Since this tag isn't defined or used in any other context, it could cause "not a known tag" errors. --- lib/jsdoc/doclet.js | 49 +++++++++++++++++++++++-- lib/jsdoc/name.js | 8 +++- lib/jsdoc/tag/dictionary/definitions.js | 8 +++- test/specs/jsdoc/doclet.js | 25 ++++++++++++- test/specs/jsdoc/name.js | 30 +++++++++++++++ 5 files changed, 113 insertions(+), 7 deletions(-) diff --git a/lib/jsdoc/doclet.js b/lib/jsdoc/doclet.js index 98b1e86e..b4f88974 100644 --- a/lib/jsdoc/doclet.js +++ b/lib/jsdoc/doclet.js @@ -24,6 +24,7 @@ var jsdoc = { } }; var path = require('jsdoc/path'); +var util = require('util'); // Longname used for doclets whose actual longname cannot be identified. @@ -46,10 +47,6 @@ function applyTag(doclet, tag) { if (tag.title === 'description') { doclet.description = tag.value; } - - if (tag.title === 'scope') { - doclet.scope = tag.value; - } } // use the meta info about the source code to guess what the doclet kind should be @@ -250,6 +247,50 @@ Doclet.prototype.setLongname = function(name) { } }; +/** + * Get the full path to the source file that is associated with a doclet. + * + * @private + * @param {module:jsdoc/doclet.Doclet} The doclet to check for a filepath. + * @return {string} The path to the doclet's source file, or an empty string if the path is not + * available. + */ +function getFilepath(doclet) { + if (!doclet || !doclet.meta || !doclet.meta.filename) { + return ''; + } + + return path.join(doclet.meta.path || '', doclet.meta.filename); +} + +/** + * Set the doclet's `scope` property. Must correspond to a scope name that is defined in + * {@link module:jsdoc/name.SCOPE_NAMES}. + * + * @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); + + 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); + if (filepath) { + errorMessage += util.format(' (Source file: %s)', filepath); + } + + throw new Error(errorMessage); + } + + this.scope = scope; +}; + /** * Add a symbol to this doclet's `borrowed` array. * diff --git a/lib/jsdoc/name.js b/lib/jsdoc/name.js index 5ba18a05..268a329d 100644 --- a/lib/jsdoc/name.js +++ b/lib/jsdoc/name.js @@ -14,6 +14,12 @@ var jsdoc = { }; // Scope identifiers. +var SCOPE_NAMES = exports.SCOPE_NAMES = { + global: 'global', + inner: 'inner', + instance: 'instance', + 'static': 'static' +}; var INNER = exports.INNER = '~'; var INSTANCE = exports.INSTANCE = '#'; var STATIC = exports.STATIC = '.'; @@ -26,7 +32,7 @@ var puncToScope = exports.puncToScope = _.invert(scopeToPunc); var MODULE_PREFIX = exports.MODULE_PREFIX = 'module:'; -var DEFAULT_SCOPE = 'static'; +var DEFAULT_SCOPE = SCOPE_NAMES.static; /** Resolves the longname, memberof, variation and name values of the given doclet. diff --git a/lib/jsdoc/tag/dictionary/definitions.js b/lib/jsdoc/tag/dictionary/definitions.js index 176aa5fc..0e74de84 100644 --- a/lib/jsdoc/tag/dictionary/definitions.js +++ b/lib/jsdoc/tag/dictionary/definitions.js @@ -8,6 +8,7 @@ */ 'use strict'; +var logger = require('jsdoc/util/logger'); var path = require('jsdoc/path'); var Syntax = require('jsdoc/src/syntax').Syntax; @@ -35,7 +36,12 @@ function setDocletKindToTitle(doclet, tag) { } function setDocletScopeToTitle(doclet, tag) { - doclet.addTag( 'scope', tag.title ); + try { + doclet.setScope(tag.title); + } + catch(e) { + logger.error(e.message); + } } function setDocletNameToValue(doclet, tag) { diff --git a/test/specs/jsdoc/doclet.js b/test/specs/jsdoc/doclet.js index efeaed25..2edd0642 100644 --- a/test/specs/jsdoc/doclet.js +++ b/test/specs/jsdoc/doclet.js @@ -1,7 +1,8 @@ /*global describe: true, env: true, expect: true, it: true, jasmine: true */ describe("jsdoc/doclet", function() { // TODO: more tests - + var Doclet = require('jsdoc/doclet').Doclet; + var docSet = jasmine.getDocSetFromFile('test/fixtures/doclet.js'), test1 = docSet.getByLongname('test1')[0], test2 = docSet.getByLongname('test2')[0]; @@ -13,4 +14,26 @@ describe("jsdoc/doclet", function() { expect(test2.description.indexOf(expectStrong)).toBeGreaterThan(-1); expect(test2.description.indexOf(expectList)).toBeGreaterThan(-1); }); + + describe('setScope', function() { + it('should accept the correct scope names', function() { + function setScope(scopeName) { + var doclet = new Doclet('/** Huzzah, a doclet! */', {}); + doclet.setScope(scopeName); + } + + Object.keys(require('jsdoc/name').SCOPE_NAMES).forEach(function(scopeName) { + expect( setScope.bind(null, scopeName) ).not.toThrow(); + }); + }); + + it('should throw an error for invalid scope names', function() { + function setScope() { + var doclet = new Doclet('/** Woe betide this doclet. */', {}); + doclet.setScope('fiddlesticks'); + } + + expect(setScope).toThrow(); + }); + }); }); diff --git a/test/specs/jsdoc/name.js b/test/specs/jsdoc/name.js index 80036ef7..a788a5d4 100644 --- a/test/specs/jsdoc/name.js +++ b/test/specs/jsdoc/name.js @@ -18,6 +18,12 @@ describe("jsdoc/name", function() { expect(typeof jsdoc.name.applyNamespace).toEqual("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 an 'shorten' function", function() { expect(jsdoc.name.shorten).toBeDefined(); expect(typeof jsdoc.name.shorten).toEqual("function"); @@ -28,6 +34,30 @@ describe("jsdoc/name", function() { expect(typeof jsdoc.name.splitName).toEqual("function"); }); + describe('SCOPE_NAMES', function() { + var SCOPE_NAMES = jsdoc.name.SCOPE_NAMES; + + 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'); + }); + }); + describe ("shorten", function() { it('should break up a longname into the correct memberof, name and scope parts', function() { var startName = 'lib.Panel#open',