diff --git a/lib/jsdoc/path.js b/lib/jsdoc/path.js index 9a826e8f..eb08089e 100644 --- a/lib/jsdoc/path.js +++ b/lib/jsdoc/path.js @@ -42,6 +42,7 @@ function prefixReducer(previousPath, current) { * * For example, assuming that the current working directory is `/Users/jsdoc`: * + * + For the single path `foo/bar/baz/qux.js`, the common prefix is `foo/bar/baz/`. * + For paths `foo/bar/baz/qux.js`, `foo/bar/baz/quux.js`, and `foo/bar/baz.js`, the common prefix * is `/Users/jsdoc/foo/bar/`. * + For paths `../jsdoc/foo/bar/baz/qux/quux/test.js`, `/Users/jsdoc/foo/bar/bazzy.js`, and @@ -53,18 +54,34 @@ 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; + var segments; + + var prefix = ''; 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 - if ( common.length && (common.length > 1 || common[0] !== '') ) { - common.push(''); + // if there's only one path, its resolved dirname (plus a trailing slash) is the common prefix + if (paths.length === 1) { + prefix = path.resolve(global.env.pwd, paths[0]); + if ( path.extname(prefix) ) { + prefix = path.dirname(prefix); + } + + prefix += path.sep; + } + else { + segments = paths.reduce(prefixReducer, undefined) || []; + + // if there's anything left (other than a placeholder for a leading slash), add a + // placeholder for a trailing slash + if ( segments.length && (segments.length > 1 || segments[0] !== '') ) { + segments.push(''); + } + + prefix = segments.join(path.sep); } - return common.join(path.sep); + return prefix; }; /** diff --git a/lib/jsdoc/tag/dictionary/definitions.js b/lib/jsdoc/tag/dictionary/definitions.js index 568c8fdd..176aa5fc 100644 --- a/lib/jsdoc/tag/dictionary/definitions.js +++ b/lib/jsdoc/tag/dictionary/definitions.js @@ -14,9 +14,13 @@ var Syntax = require('jsdoc/src/syntax').Syntax; function filepathMinusPrefix(filepath) { var sourceFiles = env.sourceFiles || []; var commonPrefix = path.commonPrefix( sourceFiles.concat(env.opts._ || []) ); - // always use forward slashes - var result = (filepath + path.sep).replace(commonPrefix, '') - .replace(/\\/g, '/'); + var result = ''; + + if (filepath) { + // always use forward slashes + result = (filepath + path.sep).replace(commonPrefix, '') + .replace(/\\/g, '/'); + } if (result.length > 0 && result[result.length - 1] !== '/') { result += '/'; diff --git a/rhino/path.js b/rhino/path.js index 4d29be0b..c4218f93 100644 --- a/rhino/path.js +++ b/rhino/path.js @@ -102,12 +102,35 @@ function normalizeArray(parts, allowAboveRoot) { return parts; } +exports.extname = function(path) { + return splitPath(path)[3]; +}; + if (isWindows) { // Regex to split a windows path into three parts: [*, device, slash, // tail] windows-only var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/; + // Regex to split the tail part of the above into [*, dir, basename, ext] + var splitTailRe = + /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; + + // Function to split a filename into [root, dir, basename, ext] + // windows version + var splitPath = function(filename) { + // Separate device+slash from tail + var result = splitDeviceRe.exec(filename), + device = (result[1] || '') + (result[2] || ''), + tail = result[3] || ''; + // Split the tail into dir, basename and extension + var result2 = splitTailRe.exec(tail), + dir = result2[1], + basename = result2[2], + ext = result2[3]; + return [device, dir, basename, ext]; + }; + // path.resolve([from ...], to) // windows version exports.resolve = function() { @@ -292,6 +315,14 @@ if (isWindows) { return outputParts.join('\\'); }; } else { + // Split a filename into [root, dir, basename, ext], unix version + // 'root' is just a slash, or nothing. + var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); + }; + // path.resolve([from ...], to) // posix version exports.resolve = function() { diff --git a/test/specs/documentation/modules.js b/test/specs/documentation/modules.js index 63902a69..fd61cda7 100644 --- a/test/specs/documentation/modules.js +++ b/test/specs/documentation/modules.js @@ -6,22 +6,31 @@ describe("module names", function() { var doclets; + var pwd = env.pwd; var srcParser = null; + var sourceFiles = env.sourceFiles.slice(0); var sourcePaths = env.opts._.slice(0); beforeEach(function() { - env.opts._ = [path.normalize(env.dirname + '/test/fixtures/modules/data/')]; + env.opts._ = [path.normalize(env.pwd + '/test/fixtures/modules/data/')]; + env.pwd = env.dirname; + env.sourceFiles = []; srcParser = jasmine.createParser(); require('jsdoc/src/handlers').attachTo(srcParser); }); afterEach(function() { env.opts._ = sourcePaths; + env.pwd = pwd; + env.sourceFiles = sourceFiles; }); it("should create a name from the file path when no documented module name exists", function() { + var filename = 'test/fixtures/modules/data/mod-1.js'; + + env.sourceFiles.push(filename); doclets = srcParser.parse( - path.normalize(env.dirname + '/test/fixtures/modules/data/mod-1.js') + path.normalize( path.join(env.pwd, filename) ) ); expect(doclets.length).toBeGreaterThan(1); expect(doclets[0].longname).toEqual('module:mod-1'); @@ -33,8 +42,6 @@ describe("module names", function() { var Doclet = require('jsdoc/doclet').Doclet; var doclet; - // setup - var sourceFiles = env.sourceFiles.slice(0); env.sourceFiles = [ 'C:\\Users\\Jane Smith\\myproject\\index.js', 'C:\\Users\\Jane Smith\\myproject\\lib\\mymodule.js' @@ -47,16 +54,17 @@ describe("module names", function() { }); expect(doclet.name).toBe('lib/mymodule'); - - // teardown - env.sourceFiles = sourceFiles; }); } it("should use the documented module name if available", function() { + var filename = 'test/fixtures/modules/data/mod-2.js'; + + env.sourceFiles.push(filename); doclets = srcParser.parse( - path.normalize(env.dirname + '/test/fixtures/modules/data/mod-2.js') + path.normalize( path.join(env.pwd, filename) ) ); + expect(doclets.length).toBeGreaterThan(1); expect(doclets[0].longname).toEqual('module:my/module/name'); }); diff --git a/test/specs/jsdoc/path.js b/test/specs/jsdoc/path.js index ea0d0c59..24ed9aeb 100644 --- a/test/specs/jsdoc/path.js +++ b/test/specs/jsdoc/path.js @@ -46,6 +46,14 @@ describe('jsdoc/path', function() { global.env.pwd = oldPwd; }); + it('finds the correct prefix for a single relative path', function() { + var paths = [path.join('foo', 'bar', 'baz', 'qux.js')]; + // we expect a trailing slash + var expected = cwd.concat('foo', 'bar', 'baz', '').join(path.sep); + + expect( path.commonPrefix(paths) ).toBe(expected); + }); + it('finds the correct prefix for a group of relative paths', function() { var paths = [ path.join('foo', 'bar', 'baz', 'qux.js'), @@ -58,6 +66,14 @@ describe('jsdoc/path', function() { expect( path.commonPrefix(paths) ).toEqual(expected); }); + it('finds the correct prefix for a single absolute path', function() { + var paths = [cwd.concat('foo', 'bar', 'baz', 'qux.js').join(path.sep)]; + // we expect a trailing slash + var expected = cwd.concat('foo', 'bar', 'baz', '').join(path.sep); + + expect( path.commonPrefix(paths) ).toBe(expected); + }); + it('finds the correct prefix for a group of absolute paths', function() { var paths = [ cwd.concat('foo', 'bar', 'baz', 'qux.js').join(path.sep), @@ -83,6 +99,12 @@ describe('jsdoc/path', function() { expect( path.commonPrefix(paths) ).toEqual(expected); }); + it('returns an empty string when the paths array is empty', function() { + var paths = []; + + expect( path.commonPrefix(paths) ).toBe(''); + }); + // skip on Windows, since the paths share a drive letter at the start if (!isWindows) { it('returns an empty string when there is no common prefix', function() { diff --git a/test/specs/tags/overviewtag.js b/test/specs/tags/overviewtag.js index 22272dbf..d2d85cf1 100644 --- a/test/specs/tags/overviewtag.js +++ b/test/specs/tags/overviewtag.js @@ -7,26 +7,42 @@ describe("@overview tag", function() { var doclets; + var pwd = env.pwd; var srcParser = null; + var sourceFiles = env.sourceFiles.slice(0); var sourcePaths = env.opts._.slice(0); beforeEach(function() { - env.opts._ = [path.normalize(env.dirname + '/test/fixtures/')]; + env.opts._ = [path.normalize(env.pwd + '/test/fixtures/')]; + env.pwd = env.dirname; + env.sourceFiles = []; srcParser = jasmine.createParser(); require('jsdoc/src/handlers').attachTo(srcParser); }); afterEach(function() { env.opts._ = sourcePaths; + env.pwd = pwd; + env.sourceFiles = sourceFiles; }); 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( path.normalize(env.dirname + '/test/fixtures/file.js') ); + var filename = 'test/fixtures/file.js'; + + env.sourceFiles.push(filename); + doclets = srcParser.parse( + path.normalize( path.join(env.pwd, filename) ) + ); expect(doclets[0].name).toMatch(/^file\.js$/); }); it("The name and longname should be equal", function() { - doclets = srcParser.parse( path.normalize(env.dirname + '/test/fixtures/file.js') ); + var filename = 'test/fixtures/file.js'; + + env.sourceFiles.push(filename); + doclets = srcParser.parse( + path.normalize( path.join(env.pwd, filename) ) + ); expect(doclets[0].name).toBe(doclets[0].longname); }); });