diff --git a/jsdoc.js b/jsdoc.js index b1f27885..6312f1c9 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -61,6 +61,13 @@ require('lib/jsdoc/util/global').env = { */ opts: {}, + /** + * The source files that JSDoc will parse. + * @type Array + * @memberof env + */ + sourceFiles: [], + /** * The JSDoc version number and revision date. * @@ -220,7 +227,8 @@ function main() { if (env.conf.source && env.opts._.length > 0) { // are there any files to scan and parse? filter = new jsdoc.src.filter.Filter(env.conf.source); - sourceFiles = app.jsdoc.scanner.scan(env.opts._, (env.opts.recurse? 10 : undefined), filter); + env.sourceFiles = sourceFiles = app.jsdoc.scanner.scan(env.opts._, + (env.opts.recurse? 10 : undefined), filter); jsdoc.src.handlers.attachTo(app.jsdoc.parser); diff --git a/lib/jsdoc/path.js b/lib/jsdoc/path.js index 4ddb031d..ea405e8c 100644 --- a/lib/jsdoc/path.js +++ b/lib/jsdoc/path.js @@ -51,7 +51,10 @@ function prefixReducer(previousPath, current) { * @return {string} The common prefix, or an empty string if there is no common prefix. */ exports.commonPrefix = function(paths) { - var common = paths.reduce(prefixReducer, undefined); + var common; + + paths = paths || []; + common = paths.reduce(prefixReducer, undefined) || []; // if there's anything left (other than a placeholder for a leading slash), add a placeholder // for a trailing slash diff --git a/lib/jsdoc/tag/dictionary/definitions.js b/lib/jsdoc/tag/dictionary/definitions.js index cb96cbf7..b37e0753 100644 --- a/lib/jsdoc/tag/dictionary/definitions.js +++ b/lib/jsdoc/tag/dictionary/definitions.js @@ -7,7 +7,19 @@ @license Apache License 2.0 - See file 'LICENSE.md' in this project. */ -var path = require('path'); +var path = require('jsdoc/path'); + +function filepathMinusPrefix(filepath) { + var sourceFiles = env.sourceFiles || []; + var commonPrefix = path.commonPrefix( sourceFiles.concat(env.opts._ || []) ); + var result = (filepath + '/').replace(commonPrefix, ''); + + if (result.length > 0 && result[result.length - 1] !== '/') { + result += '/'; + } + + return result; +} /** @private */ function setDocletKindToTitle(doclet, tag) { @@ -34,12 +46,11 @@ function setDocletDescriptionToValue(doclet, tag) { } function setNameToFile(doclet, tag) { - var name = ''; + var name; + if (doclet.meta.filename) { - // TODO: find the shortest path shared by all input files, and remove that from - // doclet.meta.path - name += path.basename(doclet.meta.path) + '/'; - doclet.addTag( 'name', name + doclet.meta.filename ); + name = filepathMinusPrefix(doclet.meta.path) + doclet.meta.filename; + doclet.addTag('name', name); } } @@ -64,17 +75,13 @@ function applyNamespace(docletOrNs, tag) { } function setDocletNameToFilename(doclet, tag) { - // TODO: find the shortest path shared by all input files, and remove that from doclet.meta.path - var name = doclet.meta.path ? path.basename(doclet.meta.path) + '/' : ''; - name += doclet.meta.filename; - name = name.replace(/\.js$/i, ''); - - for (var i = 0, len = env.opts._.length; i < len; i++) { - if (name.indexOf(env.opts._[i]) === 0) { - name = name.replace(env.opts._[0], ''); - break; - } + var name = ''; + + if (doclet.meta.path) { + name = filepathMinusPrefix(doclet.meta.path); } + name += doclet.meta.filename.replace(/\.js$/i, ''); + doclet.name = name; } diff --git a/lib/jsdoc/util/templateHelper.js b/lib/jsdoc/util/templateHelper.js index c85a5665..c14f33cb 100644 --- a/lib/jsdoc/util/templateHelper.js +++ b/lib/jsdoc/util/templateHelper.js @@ -52,6 +52,8 @@ function makeFilenameUnique(filename, str) { } function cleanseFilename(str) { + str = str || ''; + // allow for namespace prefix return str.replace(/^(event|module|external|package):/, '$1-') // use - instead of ~ to denote 'inner' @@ -702,28 +704,46 @@ function getFilename(longname) { /** Turn a doclet into a URL. */ exports.createLink = function(doclet) { + var filename; + var fragment; + var match; + var fakeContainer; + var url = ''; var longname = doclet.longname; - var filename; - - var fakeContainerMatch = /(\S+):/.exec(longname); + // handle doclets in which doclet.longname implies that the doclet gets its own HTML file, but + // doclet.kind says otherwise. this happens due to mistagged JSDoc (for example, a module that + // somehow has doclet.kind set to `member`). + // TODO: generate a warning (ideally during parsing!) + if (containers.indexOf(doclet.kind) === -1) { + match = /(\S+):/.exec(longname); + if (match && containers.indexOf(match[1]) !== -1) { + fakeContainer = match[1]; + } + } + // the doclet gets its own HTML file if ( containers.indexOf(doclet.kind) !== -1 || isModuleFunction(doclet) ) { - url = getFilename(longname); + filename = getFilename(longname); } - // the doclet's longname suggests that it should get its own HTML file, but doclet.kind says - // otherwise. this happens due to mistagged JSDoc (for example, a module that somehow has - // doclet.kind set to `member`). use the container's filename plus a fragment. - else if ( containers.indexOf(doclet.kind) === -1 && fakeContainerMatch && - containers.indexOf(fakeContainerMatch[1]) !== -1 ) { - url = getFilename(longname) + '#' + doclet.name; + // mistagged version of a doclet that gets its own HTML file + else if ( containers.indexOf(doclet.kind) === -1 && fakeContainer ) { + filename = getFilename(doclet.memberof || longname); + if (doclet.name === doclet.longname) { + fragment = ''; + } + else { + fragment = doclet.name || ''; + } } // the doclet is within another HTML file else { filename = getFilename(doclet.memberof || exports.globalName); - url = filename + '#' + getNamespace(doclet.kind) + doclet.name; + fragment = getNamespace(doclet.kind) + (doclet.name || ''); } + + url = fragment ? (filename + '#' + fragment) : filename; return url; }; diff --git a/test/specs/documentation/modules.js b/test/specs/documentation/modules.js index 52481150..2c025d29 100644 --- a/test/specs/documentation/modules.js +++ b/test/specs/documentation/modules.js @@ -1,14 +1,20 @@ -/*global beforeEach: true, describe: true, env: true, expect: true, it: true */ +/*global afterEach: true, beforeEach: true, describe: true, env: true, expect: true, it: true */ describe("module names", function() { - var parser = require('jsdoc/src/parser'), - srcParser = null, doclets; + var parser = require('jsdoc/src/parser'); + var srcParser = null; + var doclets; + var sourcePaths = env.opts._.slice(0); beforeEach(function() { - env.opts._ = [__dirname + '/test/fixtures/modules/']; + env.opts._ = [__dirname + '/test/fixtures/modules/data/']; srcParser = new parser.Parser(); require('jsdoc/src/handlers').attachTo(srcParser); }); + afterEach(function() { + env.opts._ = sourcePaths; + }); + it("should create a name from the file path when no documented module name exists", function() { doclets = srcParser.parse(__dirname + '/test/fixtures/modules/data/mod-1.js'); expect(doclets.length).toBeGreaterThan(1); diff --git a/test/specs/jsdoc/util/templateHelper.js b/test/specs/jsdoc/util/templateHelper.js index 3ae04851..c3266177 100644 --- a/test/specs/jsdoc/util/templateHelper.js +++ b/test/specs/jsdoc/util/templateHelper.js @@ -1379,20 +1379,53 @@ describe("jsdoc/util/templateHelper", function() { it('should create a url for a doclet with the wrong kind (caused by incorrect JSDoc tags', function() { var moduleDoclet = { kind: 'module', - longname: 'module:bar', - name: 'module:bar' + longname: 'module:baz', + name: 'module:baz' }; var badDoclet = { kind: 'member', - longname: 'module:bar', - name: 'module:bar' + longname: 'module:baz', + name: 'module:baz' }; var moduleDocletUrl = helper.createLink(moduleDoclet); var badDocletUrl = helper.createLink(badDoclet); - expect(moduleDocletUrl).toBe('module-bar.html'); - expect(badDocletUrl).toBe('module-bar.html#module:bar'); + expect(moduleDocletUrl).toBe('module-baz.html'); + expect(badDocletUrl).toBe('module-baz.html'); + }); + + it('should create a url for a function that is a member of a doclet with the wrong kind', function() { + var badModuleDoclet = { + kind: 'member', + longname: 'module:qux', + name: 'module:qux' + }; + var memberDoclet = { + kind: 'function', + name: 'frozzle', + memberof: 'module:qux', + scope: 'instance', + longname: 'module:qux#frozzle' + }; + + var badModuleDocletUrl = helper.createLink(badModuleDoclet); + var memberDocletUrl = helper.createLink(memberDoclet); + + expect(badModuleDocletUrl).toBe('module-qux.html'); + expect(memberDocletUrl).toBe('module-qux.html#frozzle'); + }); + + it('should create a url for an empty package definition', function() { + var packageDoclet = { + kind: 'package', + name: undefined, + longname: 'package:undefined' + }; + + var packageDocletUrl = helper.createLink(packageDoclet); + + expect(packageDocletUrl).toBe('global.html'); }); }); diff --git a/test/specs/tags/overviewtag.js b/test/specs/tags/overviewtag.js index 6087be5b..f78761fc 100644 --- a/test/specs/tags/overviewtag.js +++ b/test/specs/tags/overviewtag.js @@ -1,18 +1,28 @@ -/*global describe: true, env: true, expect: true, it: true */ +/*global beforeEach: true, afterEach: true, describe: true, env: true, expect: true, it: true */ describe("@overview tag", function() { - var parser = require('jsdoc/src/parser'), - srcParser = new parser.Parser(), - doclets; + var parser = require('jsdoc/src/parser'); + var srcParser = null; + var doclets; + var sourcePaths = env.opts._.slice(0); - require('jsdoc/src/handlers').attachTo(srcParser); - doclets = srcParser.parse(__dirname + '/test/fixtures/file.js'); + beforeEach(function() { + env.opts._ = [__dirname + '/test/fixtures/']; + srcParser = new parser.Parser(); + require('jsdoc/src/handlers').attachTo(srcParser); + }); + + afterEach(function() { + env.opts._ = sourcePaths; + }); it('When a file overview tag appears in a doclet, the name of the doclet should contain the path to the file.', function() { + doclets = srcParser.parse(__dirname + '/test/fixtures/file.js'); expect(doclets[0].name).toMatch(/^(fixtures[\/\\]file\.js)$/); }); it("The name and longname should be equal", function() { + doclets = srcParser.parse(__dirname + '/test/fixtures/file.js'); expect(doclets[0].name).toBe(doclets[0].longname); }); });