mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
fix name resolution when the exports tag is used on a pointer to the module's exports object (#404)
This commit is contained in:
parent
1774569850
commit
f77984df55
@ -17,6 +17,12 @@ var currentModule = null;
|
||||
var SCOPE_PUNC = jsdoc.name.SCOPE.PUNC;
|
||||
var unresolvedName = /^((?:module.)?exports|this)(\.|$)/;
|
||||
|
||||
function CurrentModule(doclet) {
|
||||
this.doclet = doclet;
|
||||
this.longname = doclet.longname;
|
||||
this.originalName = doclet.meta.code.name || '';
|
||||
}
|
||||
|
||||
function filterByLongname(doclet) {
|
||||
// you can't document prototypes
|
||||
if ( /#$/.test(doclet.longname) ) {
|
||||
@ -76,13 +82,13 @@ function createSymbolDoclet(comment, e) {
|
||||
|
||||
function setCurrentModule(doclet) {
|
||||
if (doclet.kind === 'module') {
|
||||
currentModule = doclet.longname;
|
||||
currentModule = new CurrentModule(doclet);
|
||||
}
|
||||
}
|
||||
|
||||
function setModuleScopeMemberOf(doclet) {
|
||||
// handle module symbols that are _not_ assigned to module.exports
|
||||
if (currentModule && currentModule !== doclet.name) {
|
||||
if (currentModule && currentModule.longname !== doclet.name) {
|
||||
// if we don't already know the scope, it must be an inner member
|
||||
if (!doclet.scope) {
|
||||
doclet.addTag('inner');
|
||||
@ -91,7 +97,7 @@ function setModuleScopeMemberOf(doclet) {
|
||||
// if the doclet isn't a memberof anything yet, and it's not a global, it must be a memberof
|
||||
// the current module
|
||||
if (!doclet.memberof && doclet.scope !== 'global') {
|
||||
doclet.addTag('memberof', currentModule);
|
||||
doclet.addTag('memberof', currentModule.longname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,41 +139,43 @@ function processAlias(parser, doclet, astNode) {
|
||||
doclet.postProcess();
|
||||
}
|
||||
|
||||
function findModuleMemberof(parser, doclet, astNode, nameStartsWith) {
|
||||
// TODO: separate code that resolves `this` from code that resolves the module object
|
||||
function findSymbolMemberof(parser, doclet, astNode, nameStartsWith, trailingPunc) {
|
||||
var memberof = '';
|
||||
var nameAndPunc = nameStartsWith + (trailingPunc || '');
|
||||
var scopePunc = '';
|
||||
|
||||
// remove stuff that indicates module membership (but don't touch the name `module.exports`,
|
||||
// which identifies the module object itself)
|
||||
if (doclet.name !== 'module.exports') {
|
||||
doclet.name = doclet.name.replace(unresolvedName, '');
|
||||
doclet.name = doclet.name.replace(nameAndPunc, '');
|
||||
}
|
||||
|
||||
// like /** @module foo */ exports.bar = 1;
|
||||
// or /** @module foo */ module.exports.bar = 1;
|
||||
// but not /** @module foo */ module.exports = 1;
|
||||
if ( (nameStartsWith === 'exports' || nameStartsWith === 'module.exports') &&
|
||||
doclet.name !== 'module.exports' && currentModule ) {
|
||||
memberof = currentModule;
|
||||
// like `bar` in:
|
||||
// exports.bar = 1;
|
||||
// module.exports.bar = 1;
|
||||
// module.exports = MyModuleObject; MyModuleObject.bar = 1;
|
||||
if (nameStartsWith !== 'this' && currentModule && doclet.name !== 'module.exports') {
|
||||
memberof = currentModule.longname;
|
||||
scopePunc = SCOPE_PUNC.STATIC;
|
||||
}
|
||||
// like: module.exports = 1;
|
||||
else if (doclet.name === 'module.exports' && currentModule) {
|
||||
doclet.addTag('name', currentModule);
|
||||
doclet.addTag('name', currentModule.longname);
|
||||
doclet.postProcess();
|
||||
}
|
||||
else {
|
||||
// like /** @module foo */ exports = {bar: 1};
|
||||
// or /** blah */ this.foo = 1;
|
||||
memberof = parser.resolveThis(astNode);
|
||||
scopePunc = (nameStartsWith === 'exports') ?
|
||||
SCOPE_PUNC.STATIC :
|
||||
SCOPE_PUNC.INSTANCE;
|
||||
|
||||
// like /** @module foo */ this.bar = 1;
|
||||
// like the following at the top level of a module:
|
||||
// this.foo = 1;
|
||||
if (nameStartsWith === 'this' && currentModule && !memberof) {
|
||||
memberof = currentModule;
|
||||
memberof = currentModule.longname;
|
||||
scopePunc = SCOPE_PUNC.STATIC;
|
||||
}
|
||||
else {
|
||||
scopePunc = SCOPE_PUNC.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@ -180,18 +188,26 @@ function addSymbolMemberof(parser, doclet, astNode) {
|
||||
var basename;
|
||||
var memberof;
|
||||
var memberofInfo;
|
||||
var moduleOriginalName = '';
|
||||
var resolveTargetRegExp;
|
||||
var scopePunc;
|
||||
var unresolved;
|
||||
|
||||
// TODO: is this the correct behavior, given that we don't always use the AST node?
|
||||
if (!astNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check to see if the doclet name is an unresolved reference to the module wrapper
|
||||
unresolved = unresolvedName.exec(doclet.name);
|
||||
// check to see if the doclet name is an unresolved reference to the module object, or to `this`
|
||||
// TODO: handle cases where the module object is shadowed in the current scope
|
||||
if (currentModule) {
|
||||
moduleOriginalName = '|' + currentModule.originalName;
|
||||
}
|
||||
resolveTargetRegExp = new RegExp('^((?:module.)?exports|this' + moduleOriginalName +
|
||||
')(\\.|$)');
|
||||
unresolved = resolveTargetRegExp.exec(doclet.name);
|
||||
|
||||
if (unresolved) {
|
||||
memberofInfo = findModuleMemberof(parser, doclet, astNode, unresolved[1]);
|
||||
memberofInfo = findSymbolMemberof(parser, doclet, astNode, unresolved[1], unresolved[2]);
|
||||
memberof = memberofInfo.memberof;
|
||||
scopePunc = memberofInfo.scopePunc;
|
||||
|
||||
@ -247,9 +263,9 @@ function newSymbolDoclet(parser, docletSrc, e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the scope to global unless a) the doclet is a memberof something or b) the current
|
||||
// module exports only this symbol
|
||||
if (!newDoclet.memberof && currentModule !== newDoclet.name) {
|
||||
// set the scope to global unless a) the doclet is a memberof something or b) we're in a module
|
||||
// that exports only this symbol
|
||||
if ( !newDoclet.memberof && (!currentModule || currentModule.longname !== newDoclet.name) ) {
|
||||
newDoclet.scope = 'global';
|
||||
}
|
||||
|
||||
|
||||
16
test/fixtures/exportstag7.js
vendored
Normal file
16
test/fixtures/exportstag7.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/** @exports my/shirt */
|
||||
var myShirt = exports;
|
||||
|
||||
/** A property of the module. */
|
||||
myShirt.color = 'black';
|
||||
|
||||
/** @constructor */
|
||||
myShirt.Turtleneck = function(size) {
|
||||
/** A property of the class. */
|
||||
this.size = size;
|
||||
};
|
||||
|
||||
/** Iron the turtleneck. */
|
||||
myShirt.Turtleneck.prototype.iron = function() {};
|
||||
@ -133,4 +133,43 @@ describe('@exports tag', function() {
|
||||
expect(size.memberof).toBe('module:my/shirt.Turtleneck');
|
||||
});
|
||||
});
|
||||
|
||||
describe("alias to the 'exports' object", function() {
|
||||
var docSet = jasmine.getDocSetFromFile('test/fixtures/exportstag7.js');
|
||||
var shirt = docSet.getByLongname('module:my/shirt')[0];
|
||||
var color = docSet.getByLongname('module:my/shirt.color')[0];
|
||||
var tneck = docSet.getByLongname('module:my/shirt.Turtleneck')[0];
|
||||
var size = docSet.getByLongname('module:my/shirt.Turtleneck#size')[0];
|
||||
var iron = docSet.getByLongname('module:my/shirt.Turtleneck#iron')[0];
|
||||
|
||||
it('When a symbol has an @exports tag, the doclet is aliased to "module:" + the tag value.', function() {
|
||||
expect(typeof shirt).toBe('object');
|
||||
expect(shirt.alias).toBe('my/shirt');
|
||||
expect(shirt.undocumented).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('When a symbol has an @exports tag, the doclet kind is set to module.', function() {
|
||||
expect(shirt.kind).toEqual('module');
|
||||
});
|
||||
|
||||
it('When a symbol tagged with @exports is an alias to "exports", the symbol properties are documented as members of the module.', function() {
|
||||
expect(typeof color).toBe('object');
|
||||
expect(color.memberof).toBe('module:my/shirt');
|
||||
|
||||
expect(typeof tneck).toBe('object');
|
||||
expect(tneck.memberof).toBe('module:my/shirt');
|
||||
});
|
||||
|
||||
it('When a symbol tagged with @exports is an alias to "exports", and a symbol property contains a class, the instance members of the class are documented correctly.', function() {
|
||||
expect(typeof size).toBe('object');
|
||||
expect(size.name).toBe('size');
|
||||
expect(size.memberof).toBe('module:my/shirt.Turtleneck');
|
||||
expect(size.scope).toBe('instance');
|
||||
|
||||
expect(typeof iron).toBe('object');
|
||||
expect(iron.name).toBe('iron');
|
||||
expect(iron.memberof).toBe('module:my/shirt.Turtleneck');
|
||||
expect(iron.scope).toBe('instance');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user